Skip to content

Commit 61018b2

Browse files
authored
regression of thread-safety for logging macros (ros2#393)
* add regression test for thread-safety of logging macros Signed-off-by: William Woodall <william@osrfoundation.org> * remove optimization that regressed thread-safety of logging macros Signed-off-by: William Woodall <william@osrfoundation.org> * fix references to issue pr Signed-off-by: William Woodall <william@osrfoundation.org> * add documentation about the thread-safety of rcutils_log() Signed-off-by: William Woodall <william@osrfoundation.org> Signed-off-by: William Woodall <william@osrfoundation.org>
1 parent 3485f1b commit 61018b2

3 files changed

Lines changed: 77 additions & 13 deletions

File tree

include/rcutils/logging.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,20 @@ RCUTILS_ATTRIBUTE_PRINTF_FORMAT(4, 5)
486486
* ------------------ | -------------
487487
* Allocates Memory | No, for formatted outputs <= 1023 characters
488488
* | Yes, for formatted outputs >= 1024 characters
489-
* Thread-Safe | No
489+
* Thread-Safe | Yes, with itself [1]
490490
* Uses Atomics | No
491491
* Lock-Free | Yes
492+
* <i>[1] should be thread-safe with itself but not with other logging functions</i>
493+
*
494+
* This should be thread-safe with itself, but is not thread-safe with other
495+
* logging functions that do things like set logger levels.
496+
*
497+
* \todo There are no thread-safety gurantees between this function and other
498+
* logging functions in rcutils, even though it is likely users are calling
499+
* them concurrently today.
500+
* We need to revisit these functions with respect to this issue and make
501+
* guarantees where we can, and change functions higher in the stack to
502+
* provide the thread-safety where we cannot.
492503
*
493504
* \param[in] location The pointer to the location struct or NULL
494505
* \param[in] severity The severity level

src/logging.c

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -943,18 +943,20 @@ int rcutils_logging_get_logger_effective_level(const char * name)
943943
severity = g_rcutils_logging_default_logger_level;
944944
}
945945

946-
// If the calculated severity is anything but UNSET, we place it into the hashmap for speedier
947-
// lookup next time. If the severity is UNSET, we don't bother because we are going to have to
948-
// walk the hierarchy next time anyway.
949-
if (severity != RCUTILS_LOG_SEVERITY_UNSET) {
950-
ret = add_key_to_hash_map(name, severity, false);
951-
if (ret != RCUTILS_RET_OK) {
952-
// Continue on if we failed to add the key to the hashmap.
953-
// This will affect performance but is not fatal.
954-
RCUTILS_SAFE_FWRITE_TO_STDERR(
955-
"Failed to cache severity; this is not fatal but will impact performance");
956-
}
957-
}
946+
// TODO(wjwwood): restore or replace this optimization when thread-safety is addressed
947+
// see: https://github.com/ros2/rcutils/pull/393
948+
// // If the calculated severity is anything but UNSET, we place it into the hashmap for speedier
949+
// // lookup next time. If the severity is UNSET, we don't bother because we are going to have to
950+
// // walk the hierarchy next time anyway.
951+
// if (severity != RCUTILS_LOG_SEVERITY_UNSET) {
952+
// ret = add_key_to_hash_map(name, severity, false);
953+
// if (ret != RCUTILS_RET_OK) {
954+
// // Continue on if we failed to add the key to the hashmap.
955+
// // This will affect performance but is not fatal.
956+
// RCUTILS_SAFE_FWRITE_TO_STDERR(
957+
// "Failed to cache severity; this is not fatal but will impact performance\n");
958+
// }
959+
// }
958960

959961
return severity;
960962
}

test/test_logging.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
#include <gtest/gtest.h>
1616

17+
#include <chrono>
1718
#include <string>
19+
#include <thread>
1820
#include <vector>
1921

2022
#include "./allocator_testing_utils.h"
@@ -485,3 +487,52 @@ TEST(TestLogging, test_root_logger_after_nonexistent)
485487
rcutils_logging_set_logger_level(
486488
"", original_root_severity));
487489
}
490+
491+
TEST(TestLogging, test_logging_macro_thread_safety)
492+
{
493+
// This tests whether or not using logging macros from multiple threads is safe or not.
494+
495+
// This test is based on an issue found in the optimization of the logging macros,
496+
// and therefore has a very specific trigger scenario, which is described more
497+
// in the steps below.
498+
// See: https://github.com/ros2/rcutils/pull/393
499+
500+
// This test is likely to be flakey false-positive, meaning it's possible that
501+
// it will pass even if the macros are not thread-safe and may require running
502+
// repeatedly to detect problems.
503+
504+
ASSERT_EQ(RCUTILS_RET_OK, rcutils_logging_initialize());
505+
OSRF_TESTING_TOOLS_CPP_SCOPE_EXIT(
506+
{
507+
EXPECT_EQ(RCUTILS_RET_OK, rcutils_logging_shutdown());
508+
});
509+
510+
// One of the loggers needs to be set "by the user" to trigger the optimization.
511+
ASSERT_EQ(RCUTILS_RET_OK, rcutils_logging_set_logger_level("", RCUTILS_LOG_SEVERITY_DEBUG));
512+
513+
// In threads, in a loop do a log call on many different logger names.
514+
// The message doesn't matter.
515+
std::size_t loop_count = 10;
516+
auto task = [&loop_count](std::size_t thread_number) {
517+
for (std::size_t i = 0; i < loop_count; ++i) {
518+
rcutils_log_location_t location = {"func", "file", 42u};
519+
rcutils_log(
520+
&location,
521+
RCUTILS_LOG_SEVERITY_DEBUG,
522+
("some_logger_name" + std::to_string(thread_number * i)).c_str(),
523+
"message %d", 11);
524+
}
525+
};
526+
527+
// Create many thread to increase the chance of collisions.
528+
std::vector<std::thread> threads;
529+
std::size_t number_of_threads = std::thread::hardware_concurrency() * 10;
530+
for (std::size_t i = 0; i < number_of_threads; ++i) {
531+
threads.emplace_back(task, i + 1);
532+
}
533+
534+
// Wait for threads to complete.
535+
for (auto & thread : threads) {
536+
thread.join();
537+
}
538+
}

0 commit comments

Comments
 (0)