ros / dynamic_reconfigure

BSD 3-Clause "New" or "Revised" License
48 stars 111 forks source link

"error updating parameters: don't know parameter" when invoke "rosrun dynamic_reconfigure dynparam load" #163

Open caijimin opened 3 years ago

caijimin commented 3 years ago

We use ros-kinetic-dynamic-reconfigure 1.5.49-0xenial-20180316-113241-0800. And occasionally rosrun dynamic_reconfigure dynparam load command reports error updating parameters: don't know parameter xxx. After spent some time to investigate the symptom, I think race condition exits in src/dynamic_reconfigure/client.py

168     def update_configuration(self, changes):
169         """
170         Change the server's configuration
171 
172         @param changes: dictionary of key value pairs for the parameters that are changing
173         @type  changes: {str: value}
174         """
175         # Retrieve the parameter descriptions
176         if self.param_description is None:
177             self.get_parameter_descriptions()
178 
179         # Cast the parameters to the appropriate types
180         if self.param_description is not None:
181             for name, value in list(changes.items())[:]:
182                 if name != 'groups':
183                     dest_type = self._param_types.get(name)
184                     if dest_type is None:
185                         raise DynamicReconfigureParameterException('don\'t know parameter: %s' % name)

Line 185 "error updating parameters: don't know parameter xxx” is reported because the parameter xxx is not exist in map self._param_types.

From below code excerpt, we can see self._param_types is initialized after line 338 after self.param_description is extracted in line

331.
329     def _descriptions_msg(self, msg):
330         self.group_description = decode_description(msg)
331         self.param_description = extract_params(self.group_description)
332 
333         # Build map from parameter name to type
334         self._param_types = {}
335         for p in self.param_description:
336             n, t = p.get('name'), p.get('type')
337             if n is not None and t is not None:
338                 self._param_types[n] = self._param_type_from_string(t)     <<---------
339 
340         with self._cv:
341             self._cv.notifyAll()
342         if self._description_callback is not None:
343             self._description_callback(self.param_description)

Function get_parameter_descriptions will return after self.param_description is NOT None

121     def get_parameter_descriptions(self, timeout=None):
122         """
123         UNSTABLE. Return a description of the parameters for the server.
124         Do not use this method as the type that is returned may change.
125 
126         @param timeout: time to wait before giving up
127         @type  timeout: float
128         """
129         if timeout is None or timeout == 0.0:
130             with self._cv:
131                 while self.param_description is None:
132                     if rospy.is_shutdown():
133                         return None
134                     self._cv.wait()
135         else:
136             start_time = time.time()
137             with self._cv:
138                 while self.param_description is None:
139                     if rospy.is_shutdown():
140                         return None
141                     secs_left = timeout - (time.time() - start_time)
142                     if secs_left <= 0.0:
143                         break
144                     self._cv.wait(secs_left)
145 
146         return self.param_description

But self.param_description is NOT None doesn’t mean self._param_types is successfully initialized, if thread 1 is executing Line 333 ~338, some parameters maybe not set.

333         # Build map from parameter name to type
334         self._param_types = {}
335         for p in self.param_description:
336             n, t = p.get('name'), p.get('type')
337             if n is not None and t is not None:
338                 self._param_types[n] = self._param_type_from_string(t)

Thread 2 execute line 183 will report error.

183                     dest_type = self._param_types.get(name)
184                     if dest_type is None:
185                         raise DynamicReconfigureParameterException('don\'t know parameter: %s' % name)

Could we fix it by add self._param_types_inited to indicate whether self._param_types is already initialized

@@ -76,6 +76,7 @@
         self.group_description = None

         self._param_types = None
+        self._param_types_inited = False

         self._cv = threading.Condition()

@@ -128,14 +129,14 @@
         """
         if timeout is None or timeout == 0.0:
             with self._cv:
-                while self.param_description is None:
+                while self._param_types_inited is False:
                     if rospy.is_shutdown():
                         return None
                     self._cv.wait()
         else:
             start_time = time.time()
             with self._cv:
-                while self.param_description is None:
+                while self._param_types_inited is False:
                     if rospy.is_shutdown():
                         return None
                     secs_left = timeout - (time.time() - start_time)
@@ -337,6 +338,7 @@
             if n is not None and t is not None:
                 self._param_types[n] = self._param_type_from_string(t)

+        self._param_types_inited = True
         with self._cv:
             self._cv.notifyAll()
         if self._description_callback is not None:
szalik-m commented 3 years ago

+1 to this issue. It appears more often if you use longer parameter list, which, indeed, can explained by running time of 333 ~ 338 lines.