Skip to content

Commit b50da00

Browse files
committed
Implement thread-safe logging.
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 <[email protected]>
1 parent 025f78f commit b50da00

File tree

4 files changed

+376
-14
lines changed

4 files changed

+376
-14
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ set(rcutils_sources
6363
src/process.c
6464
src/qsort.c
6565
src/repl_str.c
66+
src/rwlock.c
6667
src/sha256.c
6768
src/shared_library.c
6869
src/snprintf.c

src/logging.c

+55-14
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
#include "rcutils/time.h"
5050
#include "rcutils/types/hash_map.h"
5151

52+
#include "./rwlock.h"
53+
5254

5355
#define RCUTILS_LOGGING_SEPARATOR_CHAR '.'
5456

@@ -90,6 +92,7 @@ static const char * g_rcutils_logging_default_output_format =
9092
static rcutils_allocator_t g_rcutils_logging_allocator;
9193

9294
static rcutils_logging_output_handler_t g_rcutils_logging_output_handler = NULL;
95+
static rcutils_rwlock_t g_rcutils_logging_map_lock;
9396
static rcutils_hash_map_t g_rcutils_logging_severities_map;
9497

9598
// If this is false, attempts to use the severities map will be skipped.
@@ -645,6 +648,10 @@ rcutils_ret_t rcutils_logging_initialize_with_allocator(rcutils_allocator_t allo
645648
return RCUTILS_RET_ERROR;
646649
}
647650

651+
g_rcutils_logging_map_lock = rcutils_get_zero_initialized_rwlock();
652+
// TODO(clalancette): Error checking
653+
rcutils_rwlock_init(&g_rcutils_logging_map_lock, allocator);
654+
648655
parse_and_create_handlers_list();
649656

650657
g_rcutils_logging_severities_map_valid = true;
@@ -665,6 +672,7 @@ rcutils_ret_t rcutils_logging_shutdown(void)
665672
// Iterate over the map, getting every key so we can free it
666673
char * key = NULL;
667674
int level;
675+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
668676
rcutils_ret_t hash_map_ret = rcutils_hash_map_get_next_key_and_data(
669677
&g_rcutils_logging_severities_map, NULL, &key, &level);
670678
while (RCUTILS_RET_OK == hash_map_ret) {
@@ -688,6 +696,9 @@ rcutils_ret_t rcutils_logging_shutdown(void)
688696
rcutils_get_error_string().str);
689697
ret = RCUTILS_RET_LOGGING_SEVERITY_MAP_INVALID;
690698
}
699+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
700+
rcutils_rwlock_fini(&g_rcutils_logging_map_lock);
701+
691702
g_rcutils_logging_severities_map_valid = false;
692703
}
693704
g_num_log_msg_handlers = 0;
@@ -775,6 +786,18 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
775786
// Check if key already exists, to avoid extra memory allocation
776787
// If the key already exists, then rcutils_hash_map_set will not maintain the key we give it,
777788
// so we do not need to copy the name
789+
if (set_by_user) {
790+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
791+
} else {
792+
// In the specific case where this is not being set by the user, we know
793+
// that this is a cache set. Just try to get the lock here; if we fail,
794+
// the worst case is that we don't cache this in the map and we'll try
795+
// again next time.
796+
rcutils_ret_t lockret = rcutils_rwlock_write_trylock(&g_rcutils_logging_map_lock);
797+
if (lockret != RCUTILS_RET_OK) {
798+
return RCUTILS_RET_OK;
799+
}
800+
}
778801
bool already_exists = rcutils_hash_map_key_exists(&g_rcutils_logging_severities_map, &copy_name);
779802

780803
if (!already_exists) {
@@ -783,6 +806,7 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
783806
if (copy_name == NULL) {
784807
// Don't report an error to the error handling machinery; some uses of this function are for
785808
// caching so this is not necessarily fatal.
809+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
786810
return RCUTILS_RET_ERROR;
787811
}
788812
}
@@ -800,16 +824,21 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
800824
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
801825
"Error setting severity level for logger named '%s': %s",
802826
name, rcutils_get_error_string().str);
827+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
803828
return RCUTILS_RET_ERROR;
804829
}
805830

831+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
832+
806833
return RCUTILS_RET_OK;
807834
}
808835

809836
static rcutils_ret_t get_severity_level(const char * name, int * severity)
810837
{
838+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
811839
rcutils_ret_t ret =
812840
rcutils_hash_map_get(&g_rcutils_logging_severities_map, &name, severity);
841+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
813842
if (ret != RCUTILS_RET_OK) {
814843
// One possible response is RCUTILS_RET_NOT_FOUND, but the higher layers may be OK with that.
815844
return ret;
@@ -845,7 +874,9 @@ int rcutils_logging_get_logger_leveln(const char * name, size_t name_length)
845874
}
846875

847876
int severity;
877+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
848878
rcutils_ret_t ret = get_severity_level(short_name, &severity);
879+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
849880
g_rcutils_logging_allocator.deallocate(short_name, g_rcutils_logging_allocator.state);
850881
if (ret != RCUTILS_RET_OK) {
851882
// The error message was already set by get_severity_level
@@ -863,8 +894,10 @@ int rcutils_logging_get_logger_effective_level(const char * name)
863894
}
864895

865896
size_t hash_map_size;
897+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
866898
rcutils_ret_t hash_map_ret = rcutils_hash_map_get_size(
867899
&g_rcutils_logging_severities_map, &hash_map_size);
900+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
868901
if (hash_map_ret != RCUTILS_RET_OK) {
869902
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
870903
"Error getting severity level for logger named '%s': %s",
@@ -943,20 +976,18 @@ int rcutils_logging_get_logger_effective_level(const char * name)
943976
severity = g_rcutils_logging_default_logger_level;
944977
}
945978

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-
// }
979+
// If the calculated severity is anything but UNSET, we place it into the hashmap for speedier
980+
// lookup next time. If the severity is UNSET, we don't bother because we are going to have to
981+
// walk the hierarchy next time anyway.
982+
if (severity != RCUTILS_LOG_SEVERITY_UNSET) {
983+
ret = add_key_to_hash_map(name, severity, false);
984+
if (ret != RCUTILS_RET_OK) {
985+
// Continue on if we failed to add the key to the hashmap.
986+
// This will affect performance but is not fatal.
987+
RCUTILS_SAFE_FWRITE_TO_STDERR(
988+
"Failed to cache severity; this is not fatal but will impact performance\n");
989+
}
990+
}
960991

961992
return severity;
962993
}
@@ -990,6 +1021,13 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
9901021

9911022
size_t name_length = strlen(name);
9921023

1024+
// TODO(clalancette): We could *almost* make this a read lock, except for the fact that the loop
1025+
// below might unset keys. If we find that this write lock is a bottleneck, we could split
1026+
// that unsetting of keys into a separate function which acquires the write lock itself.
1027+
// However, don't forget that when unlocking as read and relocking as write, you have to
1028+
// double-check that the thing you were operating on still exists (since it may have been
1029+
// changed in the meantime).
1030+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
9931031
if (rcutils_hash_map_key_exists(&g_rcutils_logging_severities_map, &name)) {
9941032
// Iterate over the entire contents of the severities map, looking for entries that start with
9951033
// this key name. For any ones that match, check whether the user explicitly set them. If the
@@ -1029,6 +1067,7 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
10291067
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
10301068
"Error accessing hash map when setting logger level for '%s': %s",
10311069
name, rcutils_get_error_string().str);
1070+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10321071
return hash_map_ret;
10331072
}
10341073

@@ -1042,12 +1081,14 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
10421081
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
10431082
"Error clearing old severity level for logger named '%s': %s",
10441083
name, rcutils_get_error_string().str);
1084+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10451085
return unset_ret;
10461086
}
10471087
g_rcutils_logging_allocator.deallocate(previous_key, g_rcutils_logging_allocator.state);
10481088
}
10491089
}
10501090
}
1091+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10511092

10521093
rcutils_ret_t add_key_ret = add_key_to_hash_map(name, level, true);
10531094
if (add_key_ret != RCUTILS_RET_OK) {

0 commit comments

Comments
 (0)