tokio-rs/tokio · error
cannot create LocalSet during thread shutdown
Error message
cannot create LocalSet during thread shutdown
What it means
LocalSet::new() calls context::thread_id().expect('cannot create LocalSet during thread shutdown'). thread_id() returns None when the current thread is in the middle of TLS/thread destruction (pthread exit / thread shutdown phase), so tokio refuses to construct a LocalSet there.
Source
Thrown at tokio/src/task/local.rs:504
wake_on_schedule,
}| {
ctx.set(self.ctx.take());
wake_on_schedule.set(self.wake_on_schedule);
},
);
}
}
impl fmt::Debug for LocalEnterGuard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalEnterGuard").finish()
}
}
impl LocalSet {
/// Returns a new local task set.
pub fn new() -> LocalSet {
let owner = context::thread_id().expect("cannot create LocalSet during thread shutdown");
LocalSet {
tick: Cell::new(0),
context: Rc::new(Context {
shared: Arc::new(Shared {
local_state: LocalState {
owner,
owned: LocalOwnedTasks::new(),
local_queue: UnsafeCell::new(VecDeque::with_capacity(INITIAL_CAPACITY)),
},
queue: Mutex::new(Some(VecDeque::with_capacity(INITIAL_CAPACITY))),
waker: AtomicWaker::new(),
#[cfg(tokio_unstable)]
unhandled_panic: crate::runtime::UnhandledPanic::Ignore,
}),
unhandled_panic: Cell::new(false),
}),
_not_send: PhantomData,View on GitHub (pinned to 7d0d729d8f)
Solutions
- Create the LocalSet eagerly when the thread starts, not in Drop/destructors.
- Avoid instantiating LocalSet inside thread-local destructors or atexit handlers.
- Guard cleanup code that may run on shutdown from creating new runtimes/LocalSets.
- Refactor so the LocalSet lifetime is owned by normal scope, not tied to teardown.
Example fix
// before
thread_local! {
static LS: LocalSet = LocalSet::new(); // panics if first touch is during shutdown
}
// after
let ls = LocalSet::new(); // created in normal scope
ls.block_on(async { /* ... */ }); Defensive patterns
Strategy: validation
Validate before calling
// Construct the LocalSet in normal scope, never in Drop/destructors. let ls = tokio::task::LocalSet::new();
Try / catch
// Avoid creating LocalSet during shutdown; catch_unwind only as last resort:
std::panic::catch_unwind(|| tokio::task::LocalSet::new())
.map_err(|_| io::Error::new(io::ErrorKind::Other, "cannot create LocalSet during shutdown"))? Prevention
- Create LocalSet eagerly at thread start, not lazily.
- Never instantiate LocalSet inside thread-local destructors.
- Keep runtime/LocalSet lifetime outside teardown paths.
When it happens
Trigger: Constructing LocalSet::new() from a Drop impl, destructor, or atexit/thread-local destructor that runs during thread teardown; spawning a LocalSet from cleanup code on a dying thread.
Common situations: Lazy statics / thread-locals creating a LocalSet on first access during teardown; background thread exiting while another Drop tries to make a LocalSet; libraries that lazily initialize async runtimes in destructors.
Related errors
- Unhandled Panic behavior modified after starting LocalSet
- task panicked
- There must be more than one worker
- `JoinError` reason is not a panic.
- Can't get a task id when not inside a task
AI-assisted analysis of tokio-rs/tokio@7d0d729d8f (2026-08-11).
Data as JSON: /api/errors/952f63d8e4fe5591.
Report an issue: GitHub.