Let a logger be taken back off - #153
Open
Bilb wants to merge 2 commits into
Open
Conversation
add_logger built a sink around the caller's callback and discarded the only reference to it, so the callback could never be retired: clear_loggers drops every logger in the process, including ones this caller does not own. A consumer whose callback captures something shorter-lived than the process is then left with a registered callback pointing at freed memory. That is what session-app hit - a bridge that can be closed and reopened, logging from threads its teardown cannot join - and it had to hold the bridge weakly to work around it. add_logger now returns a handle and remove_logger takes it. The handle is a shared_ptr to a forward-declared formatted_callback_sink: spdlog stays out of the public header, and the handle still names a type, so an unrelated shared_ptr cannot be passed as one. Logging is serialised against removal, so once remove_logger returns the callback is neither running nor reachable. The C API is unchanged; it never exposed removal and adding it needs an opaque handle of its own.
Bilb
force-pushed
the
logger-handles
branch
from
September 11, 2026 04:27
13ce037 to
bca23e3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on session-foundation/liblogging#1, which adds the
oxen::log::remove_sinkthis calls.The problem
add_loggerbuilds aformatted_callback_sinkaround the caller's callback, hands it toadd_sink, and throws away the only reference to it:So the callback can never be retired.
clear_loggersis the only way out and it drops every loggerin the process, including ones this caller does not own.
A consumer whose callback captures anything shorter-lived than the process is then left with a
registered callback pointing at freed memory. That is concrete rather than hypothetical: session-app
has a bridge that can be closed and reopened, and libsession logs from network threads its teardown
cannot join. It ended up holding the bridge through a
weak_ptrpurely so that a log line arrivingafter close finds a dead pointer it can check rather than freed memory it cannot.
The change
LoggerHandleisstd::shared_ptr<oxen::log::formatted_callback_sink>with the sink onlyforward-declared: the type has to be complete to dereference it, not to hold it. So spdlog stays
out of the public header - which today keeps it out with a forward declaration of
spdlog::level::level_enum- and the handle still names one type, so an unrelatedshared_ptrcannot be handed to
remove_logger.remove_loggerreturns thecallback is neither running nor reachable and what it captured can be destroyed. That is the
property a caller needs, and the reason this is worth having over "stop calling us".
ABI
add_logger's return type changes fromvoid. Source-compatible - callers that ignore it stillcompile - but the mangled symbol is unchanged while the calling convention is not, so a consumer
linking a prebuilt library against an old header would be wrong in a way the linker will not catch.
Everything I know of vendors libsession as a submodule and builds it with the application, so this
should have no victim, but it is the reviewer's call and I am happy to add a separately named
function instead.
The C API is untouched. It never exposed removal, and adding it needs an opaque handle of its own.
Verified
Built against liblogging's
remove-sinkbranch: 245/246 targets, no warnings from these files, andtestLoggingpasses. The one target that does not link,static-bundle-test, does not link beforethis change either - its link line is missing SQLiteCpp.
Targeted
clientrather thandevbecauseclientis 434 commits ahead anddevhas nothing itlacks.