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

  1. Ensure logging init is called exactly once, guarded by a `std::sync::Once` or equivalent, before spawning threads that may also init.
  2. Check for a prior partial init failure: if init errored previously, the process state may be inconsistent — restart or restructure to avoid retrying init.
  3. Serialize initialization behind a mutex/once cell in application startup code.
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/85900406940eb918. Report an issue: GitHub.