Skip to content

Commit e6b4fbc

Browse files
authored
perf(gateway): read the log sink through a raw pointer
A log call took a std::shared_ptr copy of the sink through std::atomic_load, which locks a mutex from a process-wide pool and changes the reference count, and the call to enabled() did it a second time. The slot now holds a std::atomic<Sink *> that the log path reads, and set_sink keeps every installed sink alive until the process exits, so a thread that is logging never uses a freed sink. Sink::enabled and Sink::write take the logger name as const std::string &, so the ROS sink passes c_str() to rcutils without building a string.
1 parent 4cae644 commit e6b4fbc

4 files changed

Lines changed: 58 additions & 24 deletions

File tree

‎src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/logging.hpp‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414

1515
#pragma once
1616

17+
#include <atomic>
1718
#include <memory>
19+
#include <mutex>
1820
#include <string>
1921
#include <string_view>
22+
#include <vector>
2023

2124
namespace ros2_medkit_gateway::log {
2225

@@ -40,13 +43,14 @@ class Sink {
4043
Sink & operator=(Sink &&) = delete;
4144
virtual ~Sink() = default;
4245

43-
virtual bool enabled(std::string_view logger, Level level) const = 0;
44-
virtual void write(std::string_view logger, Level level, std::string_view message, const Location & location) = 0;
46+
virtual bool enabled(const std::string & logger, Level level) const = 0;
47+
virtual void write(const std::string & logger, Level level, std::string_view message, const Location & location) = 0;
4548
};
4649

4750
/// Replaces the active sink. Call it once at startup, before other threads log.
4851
/// A null sink restores the default sink, which writes "[LEVEL] [name]: message"
49-
/// to stderr for INFO and above.
52+
/// to stderr for INFO and above. Every installed sink stays alive until the process
53+
/// exits, because a thread that is logging may still use the sink it read before the call.
5054
void set_sink(std::shared_ptr<Sink> sink);
5155

5256
/// A named log source. Cheap to copy.
@@ -65,7 +69,26 @@ class Logger {
6569
namespace detail {
6670
/// Process-wide sink slot. Default visibility, so a plugin that links this code
6771
/// binds to the executable's slot.
68-
__attribute__((visibility("default"))) std::shared_ptr<Sink> & sink_slot();
72+
struct SinkSlot {
73+
SinkSlot();
74+
SinkSlot(const SinkSlot &) = delete;
75+
SinkSlot & operator=(const SinkSlot &) = delete;
76+
SinkSlot(SinkSlot &&) = delete;
77+
SinkSlot & operator=(SinkSlot &&) = delete;
78+
~SinkSlot() = default;
79+
80+
/// Makes @p sink the active sink and keeps it alive for the process.
81+
void install(std::shared_ptr<Sink> sink);
82+
83+
/// The sink the log calls use. Never null.
84+
std::atomic<Sink *> active{nullptr};
85+
86+
private:
87+
std::mutex mutex_;
88+
std::vector<std::shared_ptr<Sink>> installed_;
89+
};
90+
91+
__attribute__((visibility("default"))) SinkSlot & sink_slot();
6992

7093
void emit(const Logger & logger, Level level, const Location & location, const char * fmt, ...)
7194
__attribute__((format(printf, 4, 5)));

‎src/ros2_medkit_gateway/include/ros2_medkit_gateway/ros2/ros2_log_sink.hpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ namespace ros2_medkit_gateway {
2525
/// control (`--log-level <name>:=debug`) and `/rosout` behave as with rclcpp.
2626
class Ros2LogSink : public log::Sink {
2727
public:
28-
bool enabled(std::string_view logger, log::Level level) const override;
29-
void write(std::string_view logger, log::Level level, std::string_view message,
28+
bool enabled(const std::string & logger, log::Level level) const override;
29+
void write(const std::string & logger, log::Level level, std::string_view message,
3030
const log::Location & location) override;
3131
};
3232

‎src/ros2_medkit_gateway/src/core/logging.cpp‎

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
#include "ros2_medkit_gateway/core/logging.hpp"
1616

17-
#include <atomic>
1817
#include <cstdarg>
1918
#include <cstdio>
2019
#include <utility>
@@ -42,17 +41,18 @@ const char * level_name(Level level) {
4241

4342
class StderrSink : public Sink {
4443
public:
45-
bool enabled(std::string_view /*logger*/, Level level) const override {
44+
bool enabled(const std::string & /*logger*/, Level level) const override {
4645
return level >= Level::kInfo;
4746
}
48-
void write(std::string_view logger, Level level, std::string_view message, const Location & /*location*/) override {
49-
std::fprintf(stderr, "[%s] [%.*s]: %.*s\n", level_name(level), static_cast<int>(logger.size()), logger.data(),
50-
static_cast<int>(message.size()), message.data());
47+
void write(const std::string & logger, Level level, std::string_view message,
48+
const Location & /*location*/) override {
49+
std::fprintf(stderr, "[%s] [%s]: %.*s\n", level_name(level), logger.c_str(), static_cast<int>(message.size()),
50+
message.data());
5151
}
5252
};
5353

54-
std::shared_ptr<Sink> current_sink() {
55-
return std::atomic_load(&detail::sink_slot());
54+
Sink & current_sink() {
55+
return *detail::sink_slot().active.load(std::memory_order_acquire);
5656
}
5757

5858
} // namespace
@@ -61,20 +61,31 @@ void set_sink(std::shared_ptr<Sink> sink) {
6161
if (!sink) {
6262
sink = std::make_shared<StderrSink>();
6363
}
64-
std::atomic_store(&detail::sink_slot(), std::move(sink));
64+
detail::sink_slot().install(std::move(sink));
6565
}
6666

6767
Logger::Logger(std::string name) : name_(std::move(name)) {
6868
}
6969

7070
bool Logger::enabled(Level level) const {
71-
return current_sink()->enabled(name_, level);
71+
return current_sink().enabled(name_, level);
7272
}
7373

7474
namespace detail {
7575

76-
std::shared_ptr<Sink> & sink_slot() {
77-
static std::shared_ptr<Sink> slot = std::make_shared<StderrSink>();
76+
SinkSlot::SinkSlot() {
77+
install(std::make_shared<StderrSink>());
78+
}
79+
80+
void SinkSlot::install(std::shared_ptr<Sink> sink) {
81+
std::lock_guard<std::mutex> lock(mutex_);
82+
Sink * raw = sink.get();
83+
installed_.push_back(std::move(sink));
84+
active.store(raw, std::memory_order_release);
85+
}
86+
87+
SinkSlot & sink_slot() {
88+
static SinkSlot slot;
7889
return slot;
7990
}
8091

@@ -90,8 +101,8 @@ void emit(const Logger & logger, Level level, const Location & location, const c
90101
std::vsnprintf(buffer.data(), buffer.size(), fmt, args);
91102
}
92103
va_end(args);
93-
current_sink()->write(logger.name(), level,
94-
std::string_view(buffer.data(), needed > 0 ? static_cast<size_t>(needed) : 0), location);
104+
current_sink().write(logger.name(), level,
105+
std::string_view(buffer.data(), needed > 0 ? static_cast<size_t>(needed) : 0), location);
95106
}
96107

97108
} // namespace detail

‎src/ros2_medkit_gateway/src/ros2/ros2_log_sink.cpp‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818

1919
namespace ros2_medkit_gateway {
2020

21-
bool Ros2LogSink::enabled(std::string_view logger, log::Level level) const {
22-
return rcutils_logging_logger_is_enabled_for(std::string(logger).c_str(), static_cast<int>(level));
21+
bool Ros2LogSink::enabled(const std::string & logger, log::Level level) const {
22+
return rcutils_logging_logger_is_enabled_for(logger.c_str(), static_cast<int>(level));
2323
}
2424

25-
void Ros2LogSink::write(std::string_view logger, log::Level level, std::string_view message,
25+
void Ros2LogSink::write(const std::string & logger, log::Level level, std::string_view message,
2626
const log::Location & location) {
2727
const rcutils_log_location_t rcutils_location = {location.function, location.file,
2828
static_cast<size_t>(location.line)};
29-
rcutils_log(&rcutils_location, static_cast<int>(level), std::string(logger).c_str(), "%.*s",
30-
static_cast<int>(message.size()), message.data());
29+
rcutils_log(&rcutils_location, static_cast<int>(level), logger.c_str(), "%.*s", static_cast<int>(message.size()),
30+
message.data());
3131
}
3232

3333
} // namespace ros2_medkit_gateway

0 commit comments

Comments
 (0)