nautechsystems/nautilus_trader · error · anyhow::Error
Global logging sender was already published
Error message
Global logging sender was already published
What it means
During logging initialization, the global LOGGER_TX channel sender is published via a once-only slot. If initialization detects the sender was already published (a prior init published it) while the current init cannot proceed, it tears down the threads it started, marks the lifecycle Terminated, and bails. This guards against double publication of the global sender.
Source
Thrown at crates/common/src/logging/logger.rs:987
#[cfg(all(test, not(all(feature = "simulation", madsim))))]
if let Some(hook) = INIT_PUBLISH_HOOK.lock().take() {
let _ = hook.reached.send(());
let _ = hook.resume.recv();
}
// Store the sender globally so additional guards can be created
if let Err(tx) = LOGGER_TX.set(tx) {
#[cfg(not(all(feature = "simulation", madsim)))]
{
let _ = tx.send(LogEvent::Close);
if handle.thread().id() != std::thread::current().id() {
let _ = handle.join();
}
}
drop(tx);
*lifecycle = LoggerLifecycle::Terminated;
anyhow::bail!("Global logging sender was already published");
}
if config.bypass_logging {
super::logging_set_bypass();
}
let is_colored = config.is_colored;
let print_config = config.print_config;
if print_config {
println!("STATIC_MAX_LEVEL={STATIC_MAX_LEVEL}");
println!("Logger initialized with {config:?} {file_config:?}");
}
#[cfg(not(all(feature = "simulation", madsim)))]
{
// Store the handle globally
let mut handle_guard = LOGGER_HANDLE.lock();View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure logging init is called exactly once, guarded by a `std::sync::Once` or equivalent, before spawning threads that may also init.
- Check for a prior partial init failure: if init errored previously, the process state may be inconsistent — restart or restructure to avoid retrying init.
- Serialize initialization behind a mutex/once cell in application startup code.
- If this occurs in tests, run logging-init tests single-threaded (e.g. `#[test]` with a global lock or serial test runner).
Example fix
// before
std::thread::spawn(|| logging_init(cfg).unwrap());
logging_init(cfg).unwrap();
// after
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| logging_init(cfg).expect("logging init")); Defensive patterns
Strategy: try-catch
Validate before calling
static LOG_INIT: std::sync::Once = std::sync::Once::new();
fn ensure_log_init(cfg: LoggingConfig) { LOG_INIT.call_once(|| logging_init(cfg).expect("logging init")); } Try / catch
if let Err(e) = logging_init(cfg) {
if e.to_string().contains("already published") {
// another thread won the init race; treat as success
} else {
return Err(e);
}
} Prevention
- Guard logging init with std::sync::Once in multi-threaded startup.
- Never spawn threads that can call logging init concurrently.
- Treat 'already published' as an idempotent success in catch handlers.
When it happens
Trigger: Calling the logging init function concurrently or twice when the first init already stored the global sender in LOGGER_TX but the lifecycle state was reset or raced, so the second init sees LOGGER_TX already set.
Common situations: Multi-threaded startup where two threads race to initialize logging, or repeated init calls after a partially failed earlier initialization.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Logging has been shut down and cannot be re-initialized
- Tracing subscriber already initialized
- Invalid spec pair: {kv}
- Logging is running without a published sender
- Execution intent {} is not prepared for canonical nonce {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/85900406940eb918.
Report an issue: GitHub.