Skip to content

Commit bd1dffa

Browse files
authored
Use filelock in spawner to avoid concurrent operations (#2202)
1 parent ee743cc commit bd1dffa

File tree

2 files changed

+52
-22
lines changed

2 files changed

+52
-22
lines changed

controller_manager/controller_manager/spawner.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
)
3333
from controller_manager.controller_manager_services import ServiceNotFoundError
3434

35+
from filelock import Timeout, FileLock
3536
import rclpy
3637
from rclpy.node import Node
3738
from rclpy.signals import SignalHandlerOptions
@@ -169,34 +170,61 @@ def main(args=None):
169170
if not os.path.isfile(param_file):
170171
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
171172

172-
node = Node("spawner_" + controller_names[0])
173+
try:
174+
spawner_node_name = "spawner_" + controller_names[0]
175+
lock = FileLock("/tmp/ros2-control-controller-spawner.lock")
176+
max_retries = 5
177+
retry_delay = 3 # seconds
178+
for attempt in range(max_retries):
179+
tmp_logger = rclpy.logging.get_logger(spawner_node_name)
180+
try:
181+
tmp_logger.debug(
182+
bcolors.OKGREEN + "Waiting for the spawner lock to be acquired!" + bcolors.ENDC
183+
)
184+
# timeout after 20 seconds and try again
185+
lock.acquire(timeout=20)
186+
tmp_logger.debug(bcolors.OKGREEN + "Spawner lock acquired!" + bcolors.ENDC)
187+
break
188+
except Timeout:
189+
tmp_logger.warn(
190+
bcolors.WARNING
191+
+ f"Attempt {attempt+1} failed. Retrying in {retry_delay} seconds..."
192+
+ bcolors.ENDC
193+
)
194+
time.sleep(retry_delay)
195+
else:
196+
tmp_logger.error(
197+
bcolors.ERROR + "Failed to acquire lock after multiple attempts." + bcolors.ENDC
198+
)
199+
return 1
173200

174-
if node.get_namespace() != "/" and args.namespace:
175-
raise RuntimeError(
176-
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
177-
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
178-
)
201+
node = Node(spawner_node_name)
179202

180-
if args.namespace:
181-
warnings.filterwarnings("always")
182-
warnings.warn(
183-
"The '--namespace' argument is deprecated and will be removed in future releases."
184-
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
185-
DeprecationWarning,
186-
)
203+
if node.get_namespace() != "/" and args.namespace:
204+
raise RuntimeError(
205+
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
206+
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
207+
)
187208

188-
spawner_namespace = args.namespace if args.namespace else node.get_namespace()
209+
if args.namespace:
210+
warnings.filterwarnings("always")
211+
warnings.warn(
212+
"The '--namespace' argument is deprecated and will be removed in future releases."
213+
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
214+
DeprecationWarning,
215+
)
189216

190-
if not spawner_namespace.startswith("/"):
191-
spawner_namespace = f"/{spawner_namespace}"
217+
spawner_namespace = args.namespace if args.namespace else node.get_namespace()
192218

193-
if not controller_manager_name.startswith("/"):
194-
if spawner_namespace and spawner_namespace != "/":
195-
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
196-
else:
197-
controller_manager_name = f"/{controller_manager_name}"
219+
if not spawner_namespace.startswith("/"):
220+
spawner_namespace = f"/{spawner_namespace}"
221+
222+
if not controller_manager_name.startswith("/"):
223+
if spawner_namespace and spawner_namespace != "/":
224+
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
225+
else:
226+
controller_manager_name = f"/{controller_manager_name}"
198227

199-
try:
200228
for controller_name in controller_names:
201229

202230
if is_controller_loaded(
@@ -375,6 +403,7 @@ def main(args=None):
375403
return 1
376404
finally:
377405
node.destroy_node()
406+
lock.release()
378407
rclpy.shutdown()
379408

380409

controller_manager/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<depend>std_msgs</depend>
3333
<depend>libstatistics_collector</depend>
3434
<depend>generate_parameter_library</depend>
35+
<exec_depend>python3-filelock</exec_depend>
3536

3637
<test_depend>ament_cmake_gmock</test_depend>
3738
<test_depend>ament_cmake_pytest</test_depend>

0 commit comments

Comments
 (0)