Pitfalls and Considerations
Below are some lessons learned when developingthe core plugins of the ROS2 CLIPS-Executive. They might be a useful read for some.
Injected functions should not be blocking
In order to ensure running a responsive CLIPS application, make sure the injected functions are executing fast. Long-lasting operations should rather be dispatched by a function and then asynchronously handled once they finish. This allows the inference engine to continue operating while heavy computations or time-consuming sub-routines are processed.
Keep your locking scopes as tight as possible
When writing complex plugins with asynchronous operations, be sure to scope your guarded regions well.
A common pitfall may occur when plugins also need to guard data structures from concurrent access using some mutex, while also handling CLIPS access.
Consider this example from cx_ros_msgs_plugin which allows interactions with ROS topics:
The asynchronous subscription callbacks adds messages and meta-data to an unordered map, which needs to be guarded by a mutex map_mtx_ as multiple write operations could occur at the same time when multi-threaded executors and re-entrant callback groups are used. Additionally, the messages are asserted as facts (holding a reference to the message) in the callback.
The ros-msgs-get-field UDF allows to retrieve fields of messages. As fields may contain messages, this again might need to store meta-data, hence it also needs to lock map_mtx_.
A bad implementation using a scoped lock for the entire scope of the callback and the entire scope of ros-msgs-get-field could cause a deadlock if the ros-msgs-get-field function is called on the left-hand side of a rule, e.g., like this:
(defrule deadlock-example
(ros-msgs-subscription (topic ?sub))
?msg-f <- (ros-msgs-message (topic ?sub) (msg-ptr ?inc-msg))
(test (and (= 0 (ros-msgs-get-field ?inc-msg "velocity"))))
...
The assertion of the fact in the callback triggers the conditional check in the rule which therefore tries to lock map_mtx_ blocked by the callback function itself. Hence, make sure to keep the scopes of any mutex as tight as possible!