{"record":{"id":"120a5d05ae579153","repo":"vectordotdev/vector","slug":"double-thread-initialization","errorCode":null,"errorMessage":"double thread initialization","messagePattern":"double thread initialization","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app.rs","lineNumber":577,"sourceCode":"}\n\npub fn build_runtime(\n    threads: Option<usize>,\n    chunk_size_events: Option<NonZeroUsize>,\n    thread_name: &str,\n) -> Result<Runtime, ExitCode> {\n    let mut rt_builder = runtime::Builder::new_multi_thread();\n    rt_builder.max_blocking_threads(20_000);\n    rt_builder.enable_all().thread_name(thread_name);\n\n    let threads = threads.unwrap_or_else(crate::num_threads);\n    if threads == 0 {\n        error!(\"The `threads` argument must be greater or equal to 1.\");\n        return Err(exitcode::CONFIG);\n    }\n    WORKER_THREADS\n        .compare_exchange(0, threads, Ordering::Acquire, Ordering::Relaxed)\n        .unwrap_or_else(|_| panic!(\"double thread initialization\"));\n    rt_builder.worker_threads(threads);\n\n    let chunk_size_events = chunk_size_events\n        .map(NonZeroUsize::get)\n        .unwrap_or(vector_lib::source_sender::DEFAULT_CHUNK_SIZE_EVENTS);\n\n    let Some(source_sender_buffer_size) = threads.checked_mul(chunk_size_events) else {\n        error!(\n            \"The `chunk_size_events` argument is too large for the configured number of threads.\"\n        );\n        return Err(exitcode::CONFIG);\n    };\n    let Some(ready_array_capacity) =\n        chunk_size_events.checked_mul(crate::topology::builder::READY_ARRAY_CAPACITY_CHUNKS)\n    else {\n        error!(\"The `chunk_size_events` argument is too large.\");\n        return Err(exitcode::CONFIG);\n    };","sourceCodeStart":559,"sourceCodeEnd":595,"githubUrl":"https://github.com/vectordotdev/vector/blob/3708c39b12a93212ed8b8d7510b4cc7769cb5864/src/app.rs#L559-L595","documentation":"app::build_runtime() records the configured worker-thread count in the process-wide WORKER_THREADS static using compare_exchange(0, threads). The first successful build sets the global; any subsequent call in the same process finds it non-zero and panics with 'double thread initialization'. The guard exists because num_threads and downstream sizing (e.g. source sender buffer sizing) depend on a single, stable worker-thread count for the process lifetime.","triggerScenarios":"Calling app::build_runtime (or running Vector's CLI main) twice in one process - e.g. a test binary that builds a runtime per case, or an embedding that restarts the 'app' layer without exec'ing a new process. threads == 0 is handled separately with a config error; only the double-init path panics.","commonSituations":"Integration tests invoking CLI entry points multiple times; wrappers that re-run Vector in-process after a config change instead of exec-ing a fresh process.","solutions":["Build the runtime once per process; on reconfiguration, re-exec the binary or run a fresh process instead of calling build_runtime again","In tests, share one runtime across cases or spawn one process per case","If you only need a runtime (not Vector's global bookkeeping), construct tokio::runtime::Builder directly instead of app::build_runtime"],"exampleFix":"// before\nlet rt1 = build_runtime(Some(4), None, \"v\")?;\nlet rt2 = build_runtime(Some(4), None, \"v\")?; // panics: double thread initialization\n\n// after\nlet rt = build_runtime(Some(4), None, \"v\")?; // build once, reuse\n// restart via re-exec if a fresh runtime is truly needed","handlingStrategy":"validation","validationCode":"static RT_BUILT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);\nif RT_BUILT.swap(true, Ordering::AcqRel) {\n    // already built once in this process; re-exec instead of calling build_runtime again\n    std::process::exit(reexec_self());\n}\nlet rt = build_runtime(threads, chunk_size_events, name)?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build Vector's runtime once per process; restart by re-exec, not by re-calling build_runtime","Share one runtime across test cases or run cases in separate processes","Use plain tokio::runtime::Builder for non-Vector runtimes to avoid the global thread-count guard"],"tags":["rust","vector","runtime","global-state","initialization","panic"],"backgroundTag":"duplicate-global-initialization","analyzedSha":"3708c39b12a93212ed8b8d7510b4cc7769cb5864","analyzedAt":"2026-08-20T07:02:18.786Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}