Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement thread-safe logging. #427

Draft
wants to merge 2 commits into
base: rolling
Choose a base branch
from

Conversation

clalancette
Copy link
Contributor

This commit introduces thread-safe logging to rcutils. In particular, what it does is to add in a read-write lock around accesses to the g_rcutils_logging_severities_map in logging.c.

This enables two things:

  1. Thread-safe writes to the hash map so that get_logger_level and set_logger_level can be called simultaneously on two different threads.
  2. The ability to cache lookups during get_logger_effective_level, so we have to do less parsing on every log call.

This introduces the concept of locking into rcutils, which we haven't had before. However, this particular use seems to be justified, since the logging subsystem can be called outside of the client libraries that could possibly do this locking at a higher level (think about the rmw implementations, for instance).

Note that this introduces a new pthread dependency within rcutils that we've not had before.

If we take this, it will fix #397

@wjwwood @fujitatomoya @sloretz FYI, I'm interested to hear your thoughts about this.

@JanStaschulat @pablogs9 I'm interested to hear whether taking a pthread dependency like this will cause problems for e.g. rclc.

@pablogs9
Copy link
Member

Hi @clalancette, thanks for taking us into account.

IMO, this change will break compatibility with the micro-ROS stack for sure. But rcutils is a very special package in that sense, there are a lot of functionalities that are not embedded-friendly so we maintain a fork that keeps track of the original repo and applies micro-ROS patches.

As far as most of the functionality is in a new .c and .h, it should be easy for us to avoid building them. Also, as far as micro-ROS does not have rcutils logging, this won't break any of our core functionalities.

We can make a quick test next week to ensure micro-ROS functionality with this branch.

CC: @Acuadros95

@clalancette
Copy link
Contributor Author

IMO, this change will break compatibility with the micro-ROS stack for sure. But rcutils is a very special package in that sense, there are a lot of functionalities that are not embedded-friendly so we maintain a fork that keeps track of the original repo and applies micro-ROS patches.

Ah, I didn't know about that. Thanks. (As a side note, it looks like at least some of the changes in those patches could probably be contributed back here. set_default_allocator looks easy enough to take, and the NO_FILESYSTEM stuff we could probably take, though I'd want to probably restructure that one a bit. The other changes I would have to take a closer look at. Just trying to reduce the number of patches you have to carry around :).)

As far as most of the functionality is in a new .c and .h, it should be easy for us to avoid building them. Also, as far as micro-ROS does not have rcutils logging, this won't break any of our core functionalities.

Good to know, thanks.

We can make a quick test next week to ensure micro-ROS functionality with this branch.

That would be fantastic, thanks!

@wjwwood
Copy link
Member

wjwwood commented Jul 17, 2023

Yeah, this kind of goes against the idea of avoiding use of system calls (env vars, mutex, threading, etc) in the lowest level libraries. I know we're not perfect on that score (especially for env vars), but it's a good guiding goal imo.

It would be better (in my opinion), to have who ever initializes logging to provide a way to handle thread safety, a la dependency injection. On special OS/hardware combos that might mean no synchronization, and in C++ that might be std::mutex, in rclc that might be an OS specific mechanism, in another use case that might be a spin-lock (trading performance for other qualities), etc.

I'll try to give the actual changes a review after lunch, but at least for now that's my general feeling for the approach.

@wjwwood
Copy link
Member

wjwwood commented Jul 17, 2023

I would be happy if your rwlock here was a feature of rcutils, and was the default if nothing else was specified, but it was possible to avoid in favor of something else at initialization time of logging.

@clalancette
Copy link
Contributor Author

It would be better (in my opinion), to have who ever initializes logging to provide a way to handle thread safety, a la dependency injection.

I'll just point out that in our current system, this is tricky. The problem is that every logging call starts with a call to RCUTILS_LOGGING_AUTOINIT, so whichever module logs first initializes the logging subsystem.

Maybe we should change that so we call RCUTILS_LOGGING_AUTOINIT during rclcpp::init or rclpy.init. That would have the benefit that a) we would know exactly when logging is initialized, and b) we would remove some branches from the logging hot-path. The downside would be that this would be a semantic change, so people using RCUTILS_LOGGING macros without calling those init functions would start failing.

Thoughts?

@JanStaschulat
Copy link

JanStaschulat commented Jul 20, 2023

Regarding micro-ROS: rclc is including <rcutils/logging_macros.h> for logging and uses macros like
RCUTILS_LOG_DEBUG_NAMED, RCUTILS_LOG_INFO, and RCUTILS_LOG_ERROR.

Introducting thread-safe logging would not be a problem for the functionality of the rclc package per se.

However, micro-ROS could be used in non-POSIX operating systems too, like AUTOSAR or bare-metal, which do not support pthread functions or data types, like pthread_rwlock_t. Those would need to be replaced by equivalent ones in the respective operating system. This could be maintained in a micro-ROS fork of rcutils.

Secondly, this locking could lead to further multi-threading issues, when the micro-ROS PR for multi-threading support and priority assignment will have been merged. Then, we will probably run into priority inversions or deadlocks, if callbacks are processed with different thread priorities and call rcutils logging API simultaneously. This pull request goes beyond the MultiThreadedExecutor capability of multi-threading, because it provides an API to assign a thread priority to callbacks directly. So callbacks can be executed by different thread priorities, which is currently not possible with ROS 2 API (alone). However, in ROS 2, people do create multiple threads outside ROS 2 and let Executors run with different thread priorities.

Apart from micro-ROS, please also consider current PRs on ROS 2:

PR to introduce a priority inheritance mutex:

PR to introduce thread creation independent of the operating system:

ROS Discourse post about real-time logging:

As a general comment, I think this topic is very important also for (a future) real-time support for ROS 2. This does not only require a real-time ROS 2 Executor and static memory allocation; but also topics like real-time logging, thread creation and configuration, priority inheritance mutex support, and other topics need to be addressed. To me, it is currently unclear, how this could be organized efficiently. Several PR have been made by the community as well as several publications have analysed the current limitations, but those improvements and new ideas need to be centrally reviewed and orchestrated so that they can find their way into the next ROS 2 release.

This commit introduces thread-safe logging to rcutils.
In particular, what it does is to add in a read-write lock
around accesses to the g_rcutils_logging_severities_map
in logging.c.

This enables two things:
1. Thread-safe writes to the hash map so that get_logger_level
and set_logger_level can be called simultaneously on two
different threads.
2. The ability to cache lookups during get_logger_effective_level,
so we have to do less parsing on every log call.

This introduces the concept of locking into rcutils, which
we haven't had before.  However, this particular use seems to
be justified, since the logging subsystem can be called outside
of the client libraries that could possibly do this locking
at a higher level (think about the rmw implementations, for
instance).

Note that this introduces a new pthread dependency within
rcutils that we've not had before.

Signed-off-by: Chris Lalancette <clalancette@gmail.com>
It is much easier to follow this way.

Signed-off-by: Chris Lalancette <clalancette@gmail.com>
@clalancette clalancette force-pushed the clalancette/thread-safe-logging branch from b50da00 to ab44fc8 Compare September 13, 2023 20:41
@clalancette
Copy link
Contributor Author

I've rebased this onto the latest, and also added in a bunch more detection logic here. It should now only attempt to build a pthread implementation when that is available, though it won't automatically build a "stub" implementation because that is inherently racy. I'm curious what people think of the latest here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make logging functionality truly thread-safe
4 participants