Skip to content

Commit 70e8eaa

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 629d554 commit 70e8eaa

File tree

4 files changed

+383
-14
lines changed

4 files changed

+383
-14
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
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

Lines changed: 62 additions & 14 deletions
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,17 @@ 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+
653+
if (rcutils_rwlock_init(&g_rcutils_logging_map_lock, allocator) != RCUTILS_RET_OK) {
654+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
655+
"Failed to initialize rwlock for logger severities [%s].", rcutils_get_error_string().str);
656+
if (rcutils_hash_map_fini(&g_rcutils_logging_severities_map) != RCUTILS_RET_OK) {
657+
RCUTILS_SAFE_FWRITE_TO_STDERR("Additionally, failed to free memory when cleaning up\n");
658+
}
659+
return RCUTILS_RET_ERROR;
660+
}
661+
648662
parse_and_create_handlers_list();
649663

650664
g_rcutils_logging_severities_map_valid = true;
@@ -665,6 +679,7 @@ rcutils_ret_t rcutils_logging_shutdown(void)
665679
// Iterate over the map, getting every key so we can free it
666680
char * key = NULL;
667681
int level;
682+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
668683
rcutils_ret_t hash_map_ret = rcutils_hash_map_get_next_key_and_data(
669684
&g_rcutils_logging_severities_map, NULL, &key, &level);
670685
while (RCUTILS_RET_OK == hash_map_ret) {
@@ -688,6 +703,9 @@ rcutils_ret_t rcutils_logging_shutdown(void)
688703
rcutils_get_error_string().str);
689704
ret = RCUTILS_RET_LOGGING_SEVERITY_MAP_INVALID;
690705
}
706+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
707+
rcutils_rwlock_fini(&g_rcutils_logging_map_lock);
708+
691709
g_rcutils_logging_severities_map_valid = false;
692710
}
693711
g_num_log_msg_handlers = 0;
@@ -775,6 +793,18 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
775793
// Check if key already exists, to avoid extra memory allocation
776794
// If the key already exists, then rcutils_hash_map_set will not maintain the key we give it,
777795
// so we do not need to copy the name
796+
if (set_by_user) {
797+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
798+
} else {
799+
// In the specific case where this is not being set by the user, we know
800+
// that this is a cache set. Just try to get the lock here; if we fail,
801+
// the worst case is that we don't cache this in the map and we'll try
802+
// again next time.
803+
rcutils_ret_t lockret = rcutils_rwlock_write_trylock(&g_rcutils_logging_map_lock);
804+
if (lockret != RCUTILS_RET_OK) {
805+
return RCUTILS_RET_OK;
806+
}
807+
}
778808
bool already_exists = rcutils_hash_map_key_exists(&g_rcutils_logging_severities_map, &copy_name);
779809

780810
if (!already_exists) {
@@ -783,6 +813,7 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
783813
if (copy_name == NULL) {
784814
// Don't report an error to the error handling machinery; some uses of this function are for
785815
// caching so this is not necessarily fatal.
816+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
786817
return RCUTILS_RET_ERROR;
787818
}
788819
}
@@ -800,16 +831,21 @@ static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_
800831
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
801832
"Error setting severity level for logger named '%s': %s",
802833
name, rcutils_get_error_string().str);
834+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
803835
return RCUTILS_RET_ERROR;
804836
}
805837

838+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
839+
806840
return RCUTILS_RET_OK;
807841
}
808842

809843
static rcutils_ret_t get_severity_level(const char * name, int * severity)
810844
{
845+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
811846
rcutils_ret_t ret =
812847
rcutils_hash_map_get(&g_rcutils_logging_severities_map, &name, severity);
848+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
813849
if (ret != RCUTILS_RET_OK) {
814850
// One possible response is RCUTILS_RET_NOT_FOUND, but the higher layers may be OK with that.
815851
return ret;
@@ -845,7 +881,9 @@ int rcutils_logging_get_logger_leveln(const char * name, size_t name_length)
845881
}
846882

847883
int severity;
884+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
848885
rcutils_ret_t ret = get_severity_level(short_name, &severity);
886+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
849887
g_rcutils_logging_allocator.deallocate(short_name, g_rcutils_logging_allocator.state);
850888
if (ret != RCUTILS_RET_OK) {
851889
// The error message was already set by get_severity_level
@@ -863,8 +901,10 @@ int rcutils_logging_get_logger_effective_level(const char * name)
863901
}
864902

865903
size_t hash_map_size;
904+
rcutils_rwlock_read_lock(&g_rcutils_logging_map_lock);
866905
rcutils_ret_t hash_map_ret = rcutils_hash_map_get_size(
867906
&g_rcutils_logging_severities_map, &hash_map_size);
907+
rcutils_rwlock_read_unlock(&g_rcutils_logging_map_lock);
868908
if (hash_map_ret != RCUTILS_RET_OK) {
869909
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
870910
"Error getting severity level for logger named '%s': %s",
@@ -943,20 +983,18 @@ int rcutils_logging_get_logger_effective_level(const char * name)
943983
severity = g_rcutils_logging_default_logger_level;
944984
}
945985

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

961999
return severity;
9621000
}
@@ -990,6 +1028,13 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
9901028

9911029
size_t name_length = strlen(name);
9921030

1031+
// TODO(clalancette): We could *almost* make this a read lock, except for the fact that the loop
1032+
// below might unset keys. If we find that this write lock is a bottleneck, we could split
1033+
// that unsetting of keys into a separate function which acquires the write lock itself.
1034+
// However, don't forget that when unlocking as read and relocking as write, you have to
1035+
// double-check that the thing you were operating on still exists (since it may have been
1036+
// changed in the meantime).
1037+
rcutils_rwlock_write_lock(&g_rcutils_logging_map_lock);
9931038
if (rcutils_hash_map_key_exists(&g_rcutils_logging_severities_map, &name)) {
9941039
// Iterate over the entire contents of the severities map, looking for entries that start with
9951040
// this key name. For any ones that match, check whether the user explicitly set them. If the
@@ -1029,6 +1074,7 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
10291074
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
10301075
"Error accessing hash map when setting logger level for '%s': %s",
10311076
name, rcutils_get_error_string().str);
1077+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10321078
return hash_map_ret;
10331079
}
10341080

@@ -1042,12 +1088,14 @@ rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
10421088
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
10431089
"Error clearing old severity level for logger named '%s': %s",
10441090
name, rcutils_get_error_string().str);
1091+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10451092
return unset_ret;
10461093
}
10471094
g_rcutils_logging_allocator.deallocate(previous_key, g_rcutils_logging_allocator.state);
10481095
}
10491096
}
10501097
}
1098+
rcutils_rwlock_write_unlock(&g_rcutils_logging_map_lock);
10511099

10521100
rcutils_ret_t add_key_ret = add_key_to_hash_map(name, level, true);
10531101
if (add_key_ret != RCUTILS_RET_OK) {

0 commit comments

Comments
 (0)