Skip to content

Regression 1.98 → 1.99: thread_local! destructors no longer run at thread exit on wasm32-wasip1-threads #163748

Closed

Description

@Brooooooklyn

thread_local! destructors stopped running at thread exit on wasm32-wasip1-threads in Rust 1.99.0. Spawned threads that own a thread_local! value with a Drop impl leak it; the destructor never runs, not even after JoinHandle::join returns.

Code

I tried this code:

use std::cell::Cell;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

struct Probe(Arc<AtomicUsize>);
impl Drop for Probe {
    fn drop(&mut self) {
        self.0.fetch_add(1, Ordering::SeqCst);
    }
}

thread_local! {
    static PROBE: Cell<Option<Probe>> = const { Cell::new(None) };
}

fn main() {
    let completed = Arc::new(AtomicUsize::new(0));
    let n = 4;
    let handles: Vec<_> = (0..n)
        .map(|_| {
            let c = Arc::clone(&completed);
            std::thread::spawn(move || PROBE.with(|slot| slot.set(Some(Probe(c)))))
        })
        .collect();
    for h in handles {
        h.join().unwrap();
    }
    let seen = completed.load(Ordering::SeqCst);
    println!("threads={n} tls_destructors_ran={seen}");
    assert_eq!(seen, n, "TLS destructors did not run on wasm32-wasip1-threads");
}
cargo +1.98.1 build --target wasm32-wasip1-threads && wasmtime run -S threads -- target/wasm32-wasip1-threads/debug/tls-probe.wasm
cargo +1.99.0 build --target wasm32-wasip1-threads && wasmtime run -S threads -- target/wasm32-wasip1-threads/debug/tls-probe.wasm

(wasmtime 35.0.0; newer wasmtime releases dropped the -S threads flag.)

I expected to see this happen: threads=4 tls_destructors_ran=4, as on 1.98.1 and every earlier release I tried.

Instead, this happened:

threads=4 tls_destructors_ran=0

thread 'main' (1) panicked at src/main.rs:30:5:
assertion `left == right` failed: TLS destructors did not run on wasm32-wasip1-threads
  left: 0
 right: 4

Version it worked on

It most recently worked on: Rust 1.98.1

rustc 1.98.1 (48a229cea 2026-09-01)

Version with regression

rustc --version --verbose:

rustc 1.99.0 (b940084d7 2026-09-28)
binary: rustc
commit-hash: b940084d7eb6a299eb4bfeb8e34901bc051e7ac4
commit-date: 2026-09-28
host: aarch64-apple-darwin
release: 1.99.0

Also reproduces on rustc 1.100.0-nightly (4aa1fbcf4 2026-09-08), and the relevant cfgs are unchanged on current master.

Cause

library/std/src/sys/thread_local/mod.rs. Before 1.99 the guard arm that selects the no-op "for now we just leak everything" implementation carved out wasm32-wasip1-threads, and the key module (pthread-key based TLS, used to trigger the destructor list at thread exit) was enabled for it:

// 1.98.1 — guard
any(
    all(target_family = "wasm", not(
        all(target_os = "wasi", target_env = "p1", target_feature = "atomics")
    )),
    target_os = "uefi", ...
) => { pub(crate) fn enable() { /* leak everything */ } }

// 1.98.1 — key
all(target_os = "wasi", target_env = "p1", target_feature = "atomics"),

#159733 switched every WASI target to the pthread-key implementation. That exposed a wasi-libc bug on wasip1/wasip2 (#160828), so #160868 reverted it for non-p3 targets — but the revert replaced the condition with not(target_env = "p3") instead of restoring the original carve-out, so wasm32-wasip1-threads (target_env = "p1", atomics enabled) now lands in the leak-everything arm and key is no longer built for it:

// 1.99.0 — guard
any(
    all(target_family = "wasm", not(target_env = "p3")),
    target_os = "uefi", ...
) => { pub(crate) fn enable() { /* leak everything */ } }

// 1.99.0 — key
all(target_os = "wasi", target_env = "p3"),

So thread_local! destructors are silently skipped on wasm32-wasip1-threads, which does have real thread exit (wasi-libc pthread_exit runs TSD destructors before the thread becomes joinable; that is what 1.98 relied on).

Suggested fix

Restore the carve-out in both arms:

// guard
all(target_family = "wasm", not(target_env = "p3"),
    not(all(target_os = "wasi", target_env = "p1", target_feature = "atomics"))),
// key
any(
    all(target_os = "wasi", target_env = "p3"),
    all(target_os = "wasi", target_env = "p1", target_feature = "atomics"),
),

Alternatively, now that wasi-sdk-34 is used on master (the condition #160868 named for dropping the workaround), #159733 could be re-applied as-is (target_os = "wasi" in both arms). The minimal carve-out restore seems like the safer candidate for a beta/stable backport.

Found via napi-rs CI, where a test asserting that worker-thread TLS destructors complete before a thread-pool shutdown() returns started failing on wasm32-wasip1-threads the day 1.99.0 shipped: https://github.com/napi-rs/napi-rs/actions/runs/37112042192/job/111172762587

@rustbot modify labels: +regression-from-stable-to-stable, +O-wasi, +T-libs, +A-thread-locals

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    A-thread-localsArea: Thread local storage (TLS)
    O-wasiOperating system: Wasi, Webassembly System Interface
    regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Oct 4, 2026
  2. added
    C-bugCategory: This is a bug.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Oct 4, 2026
  3. maxdexh commented on Oct 4, 2026

    @maxdexh
    Member
  4. alexcrichton commented on Oct 4, 2026

    @alexcrichton
    Member

    Happy to review a PR for this, but I'll note that wasm32-wasip1-threads is a tier 2 target, tests aren't run for tier 2in CI, and I'm additionally not a maintainer of that target.

  5. maxdexh commented on Oct 5, 2026

    @maxdexh
    Member

    No idea what's going on with the cfgs, but I've worked on this code before, so I'll give it a shot

    @rustbot claim

  6. added a commit that references this issue on Oct 6, 2026
    739f332
  7. added 2 commits that reference this issue on Oct 6, 2026
    55090bc
    d47b123
  8. added a commit that references this issue on Oct 6, 2026
    1b8208a
  9. added
    P-highHigh priority
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    P-highHigh priority
    on Oct 6, 2026
  10. added 3 commits that reference this issue on Oct 7, 2026
    e8c5a35
    9066e5d
    e43cbac
  11. added a commit that references this issue on Oct 8, 2026
    8c1fed7
  12. added a commit that references this issue on Oct 10, 2026
    733f53e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-thread-localsArea: Thread local storage (TLS)C-bugCategory: This is a bug.O-wasiOperating system: Wasi, Webassembly System InterfaceP-mediumMedium priorityT-libsRelevant to the library team, which will review and decide on the PR/issue.regression-from-stable-to-stablePerformance or correctness regression from one stable version to another.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions