Giter VIP home page Giter VIP logo

thread_local-rs's Introduction

thread_local

Build Status crates.io

This library provides the ThreadLocal type which allow a separate copy of an object to be used for each thread. This allows for per-object thread-local storage, unlike the standard library's thread_local! macro which only allows static thread-local storage.

Documentation

Usage

Add this to your Cargo.toml:

[dependencies]
thread_local = "1.1"

Minimum Rust version

This crate's minimum supported Rust version (MSRV) is 1.63.0.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

thread_local-rs's People

Contributors

amanieu avatar atouchet avatar burntsushi avatar cuviper avatar djc avatar eh2406 avatar ibraheemdev avatar james7132 avatar kestrer avatar llogiq avatar mgeisler avatar swatinem avatar terrarier2111 avatar tmiasko avatar yjhmelody avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

thread_local-rs's Issues

Stack Overflow on Windows only (regression 1.1.0)

Hi, maintainer of rust-skia here. Since a two days we were getting stack overflow errors with a binding generation step on Windows only on our nightly builds, and the cause was a transitive SemVer update of thread_local dependency from 1.0.1 to 1.1.0.

Sticking to 1.0.1 fixed the issue.

Here is the dependency tree:

├── bindgen v0.56.0
│   ├── bitflags v1.2.1
│   ├── cexpr v0.4.0
│   │   └── nom v5.1.2
│   │       └── memchr v2.3.4
│   │       [build-dependencies]
│   │       └── version_check v0.9.2
│   ├── clang-sys v1.0.1
│   │   ├── glob v0.3.0
│   │   ├── libc v0.2.82
│   │   └── libloading v0.6.6
│   │       └── winapi v0.3.9
│   │   [build-dependencies]
│   │   └── glob v0.3.0
│   ├── clap v2.33.3
│   │   ├── atty v0.2.14
│   │   │   └── winapi v0.3.9
│   │   ├── bitflags v1.2.1
│   │   ├── strsim v0.8.0
│   │   ├── textwrap v0.11.0
│   │   │   └── unicode-width v0.1.8
│   │   ├── unicode-width v0.1.8
│   │   └── vec_map v0.8.2
│   ├── env_logger v0.8.2
│   │   ├── atty v0.2.14 (*)
│   │   ├── humantime v2.0.1
│   │   ├── log v0.4.11
│   │   │   └── cfg-if v0.1.10
│   │   ├── regex v1.4.3
│   │   │   ├── aho-corasick v0.7.15
│   │   │   │   └── memchr v2.3.4
│   │   │   ├── memchr v2.3.4
│   │   │   ├── regex-syntax v0.6.22
│   │   │   └── thread_local v1.0.1
│   │   │       └── lazy_static v1.4.0
│   │   └── termcolor v1.1.2
│   │       └── winapi-util v0.1.5
│   │           └── winapi v0.3.9
│   ├── lazy_static v1.4.0
│   ├── lazycell v1.3.0
│   ├── log v0.4.11 (*)
│   ├── peeking_take_while v0.1.2
│   ├── proc-macro2 v1.0.24
│   │   └── unicode-xid v0.2.1
│   ├── quote v1.0.8
│   │   └── proc-macro2 v1.0.24 (*)
│   ├── regex v1.4.3 (*)
│   ├── rustc-hash v1.1.0
│   ├── shlex v0.1.1
│   └── which v3.1.1
│       └── libc v0.2.82

Any ideas what could have caused that?

`ThreaLocal::get_or` with possibility of returning a `Result`?

I want to have thread local values, that when not initialized, will lock global mutex and clone shared value. However the locking can fail, and using get_or I have no way to return Result<T, E> or anything like that. Am I missing something, and if not - could such method be added?

publish new version

The lazy static dependency has been bumped on master. Could you publish it?

Why no `get_mut`?

Is there a reason why this crate does not implement a fn get_mut(&self) -> Option<&mut T> method?

Given that the container is thread-safe I would guess that this would be safe to do and useful (I have a use case for this, which currently forces me to use a Threadsafe<RefCell<T>>: adding a runtime check where none should be needed).

Failure under loom

#[test]
#[cfg(loom)]
fn loom() {
    use thread_local::ThreadLocal;
    use loom::sync::atomic::{AtomicPtr, Ordering};
    use std::sync::Arc;

    loom::model(|| {
        let x = Arc::new(ThreadLocal::<AtomicPtr<usize>>::with_capacity(2));

        loom::thread::spawn({
            let x = x.clone();

            move || {
                for x in x.iter() {
                    unsafe { assert_eq!(*x.load(Ordering::Acquire), 1) }
                }
            }
        });

        x.get_or(|| AtomicPtr::new(Box::into_raw(Box::new(1))));
    });
}

Fails with:

thread 'tls::tests::loom' panicked at 'Causality violation: Concurrent load and mut accesses.',
/loom-0.5.4/src/rt/location.rs:115:9

Note that this is not using any loom types inside ThreadLocal.

A little soundness hole around drop order of `thread_local!(static …)`

Here's the code to reproduce:

use std::{cell::{Cell, RefCell}, thread};

use thread_local::ThreadLocal;

fn main() {
    static FOO: ThreadLocal<Cell<i32>> = ThreadLocal::new();

    // thread 1
    thread::spawn(|| {
        thread_local!(
            static ACCESSOR: RefCell<AccessFooFromNewThreadOnDrop> =
                RefCell::new(AccessFooFromNewThreadOnDrop(None));
        );
        struct AccessFooFromNewThreadOnDrop(Option<&'static Cell<i32>>);

        // FOO.get_or_default(); // uncomment this if needed, for opposite initialization order
                                 // of the thread-locals `ACCESSOR` and “whatever ThreadLocal
                                 // uses internally” [I suppose `THREAD_GUARD: ThreadGuard`]
                                 // (drop order seems different on miri vs Linux)

        ACCESSOR.with(|a| { // initialize ACCESSOR in thread 1, will be dropped at end of scope below
            a.borrow_mut().0 = Some(FOO.get_or_default()); // store a borrow of thread-local FOO value
        });

        // destructor from `thread_local` library [I suppose `THREAD_GUARD: ThreadGuard`] runs first,
        // making FOO's value in thread 1, referenced in ACCESSOR, available for re-use in new threads

        impl Drop for AccessFooFromNewThreadOnDrop {
            fn drop(&mut self) {
                // thread 2
                let t = thread::spawn(|| {
                    // succeeds, re-using Cell<i32> value from thread 1
                    let r1: &Cell<i32> = FOO.get().unwrap();
                    r1.set(42); // write-write data race, UB
                });
                let r2: &Cell<i32> = self.0.unwrap();
                r2.set(1337); // write-write data race, UB

                t.join().unwrap();
            }
        }
    })
    .join()
    .unwrap();
}

(run on rustexplorer.com [where you'll just see it compiles&runs and doesn't panic; I haven’t made the data race particularly “observable” at run-time, to keep the code more clean])

With the additional FOO.get_or_default(); line uncommented as indicated, for panic-free execution on miri, this gets reported UB as expected:

error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `unnamed-1` and (2) retag write of type `i32` on thread `unnamed-2` at alloc3418. (2) just happened here
   --> /home/frank/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:482:31
    |
482 |         mem::replace(unsafe { &mut *self.value.get() }, val)
    |                               ^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) non-atomic write on thread `unnamed-1` and (2) retag write of type `i32` on thread `unnamed-2` at alloc3418. (2) just happened here
    |
help: and (1) occurred earlier here
   --> src/main.rs:37:17
    |
37  |                 r2.set(1337); // write-write data race, UB
    |                 ^^^^^^^^^^^^
    = help: retags occur on all (re)borrows and as well as when references are copied or moved
    = help: retags permit optimizations that insert speculative reads or writes
    = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-2`:
    = note: inside `std::cell::Cell::<i32>::replace` at /home/frank/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:482:31: 482:53
    = note: inside `std::cell::Cell::<i32>::set` at /home/frank/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/cell.rs:411:9: 411:26
note: inside closure
   --> src/main.rs:34:21
    |
34  |                     r1.set(42); // write-write data race, UB
    |                     ^^^^^^^^^^

The issue here is the reasoning behind the T: Send ==> ThreadLocal<T>: Sync logic not being 100% watertight. As stated in the docs:

Note that since thread IDs are recycled when a thread exits, it is possible for one thread to retrieve the object of another thread. Since this can only occur after a thread has exited this does not lead to any race conditions.

but the “can only occur after a thread has exited” part is enforced via a thread-local (the THREAD_GUARD: ThreadGuard one if I'm not mistaken). After that gets dropped, destructors of other thread_local!(static …); items can still run. Because ThreadLocal also doesn’t come with a .with(|x| …)-style API, borrows of 'static lifetime can be created and thus a borrow of a ThreadLocal’s contents can be made available to such destructors after the value has already been marked as re-usable by other threads via the ThreadGuard dropped beforehand.

I can’t really think of any particularly satisfying alternative workarounds in implementation, i.e. without significant API-change: Technically, a with(|x| …)-style API should probably “fix” this. The get()-style API can also be kept in addition, as long as the contained type is Sync (because unlike std::thread_local!, this ThreadLocal does not suffer from issues of the contents being dropped too early, when accessed during drops of thread locals).

Iter when T: Sync

Would it be possible to add a method that iterates over shared references to each thread's value when T: Sync?

Transient ThreadLocal

I'm trying to use ThreadLocal to propagate a scoped context for an operation (kind of like a hidden parameter). I did not find a way to reset the ThreadLocal value to its uninitialized state upon exiting the operation.

I thought about using ThreadLocal<RefCell<Option>> but it seems wasteful since ThreadLocal already internally acts as a RefCell (although it only supports going from None to Some() as I understand it). Also, the scoped values will really not be required after exiting the thread, and the app thread count is undetermined (I'm building a library, threading model is undetermined as it will be provided by the app), so there should be a way to make sure that nothing is left behind.

Any suggestion would be much appreciated.

Potential solution for `get_mut` without requiring runtime checking (e.g. `RefCell`)

Based on a brief exchange with @Kestrer:

#31 (comment)

After a quick look, I think it would require a fairly significant API rework that employs the use of guards (perhaps ThreadLocalGuard and ThreadLocalMutGuard) that could maybe impl Deref and DerefMut to the underlying value. In much the same way as IterMut is implemented:

https://docs.rs/thread_local/latest/src/thread_local/lib.rs.html#468-471

Scratch that, I think I may have a working implementation.

Nope, yeah I think guards may be needed

Dropping Value in TLS

I'm trying to figure out how to do this - my problem is I have a TLS that contains some database connections that are live for the duration of the thread. What I would like do is, when the thread is being destroyed, take the value out of TLS so I can drop it (otherwise, the value just lives forever in TLS and is never cleaned up).

The only thing that seems to work is Mutex<Option<T>>, but reaching for a Mutex here seems like overkill. RefCell/Rc doesn't work because of the Send requirement. Anything I might be overlooking here?

Swap to using std's OnceCell once stablized

OnceCell is set to be partially stablized in Rust 1.70. (rust-lang/rust#105587).

If it releases as a part of Rust 1.70 and it's acceptable to bump the MSRV of this crate to it, it may be possible to replace the once_cell dependency of this crate with the stdlib's implementation.

1.0 release?

I feel like the API of this crate has been stable for quite some time. I've used it in places other than regex on occasion, and it has always worked well.

What do you think about calling it time for 1.0?

Use of undeclared type or module `mem` on emscripten

I was trying to compile thread_local 0.3.1 on emscripten and getting error:

error[E0433]: failed to resolve. Use of undeclared type or module `mem`
   --> /home/nsun/.cargo/registry/src/github.com-1ecc6299db9ec823/thread_local-0.3.1/src/lib.rs:160:45
    |
160 |     thread_local!(static KEY: u8 = unsafe { mem::uninitialized() });
    |                                             ^^^^^^^^^^^^^^^^^^ Use of undeclared type or module `mem`

Documentation update?

According to the documentation, Thread Id may be reused. However, according to the standard library, "ThreadIds are guaranteed not to be reused, even when a thread terminates."

Is this a documentation mistake?

thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value' on FreeBSD

thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /.cargo/registry/src/github.com-1ecc6299db9ec823/thread_local-1.1.6/src/thread_id.rs:135:65
stack backtrace:
   0: rust_begin_unwind
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/std/src/panicking.rs:575:5
   1: core::panicking::panic_fmt
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/panicking.rs:64:14
   2: core::panicking::panic
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/panicking.rs:111:5
   3: core::option::Option<T>::unwrap
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:778:21
   4: <thread_local::thread_id::ThreadGuard as core::ops::drop::Drop>::drop
             at /.cargo/registry/src/github.com-1ecc6299db9ec823/thread_local-1.1.6/src/thread_id.rs:135:30
   5: core::ptr::drop_in_place<thread_local::thread_id::ThreadGuard>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
   6: core::ptr::drop_in_place<core::option::Option<thread_local::thread_id::ThreadGuard>>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
   7: core::ptr::drop_in_place<core::cell::UnsafeCell<core::option::Option<thread_local::thread_id::ThreadGuard>>>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
   8: core::ptr::drop_in_place<std::thread::local::lazy::LazyKeyInner<thread_local::thread_id::ThreadGuard>>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
   9: core::ptr::drop_in_place<std::thread::local::os::Value<thread_local::thread_id::ThreadGuard>>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
  10: core::ptr::drop_in_place<alloc::boxed::Box<std::thread::local::os::Value<thread_local::thread_id::ThreadGuard>>>
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ptr/mod.rs:490:1
  11: core::mem::drop
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/mem/mod.rs:979:24
  12: std::thread::local::os::destroy_value
             at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/std/src/thread/local.rs:1144:13
  13: _thread_cleanupspecific
             at /usr/src/lib/libthr/thread/thr_spec.c:155:5
  14: exit_thread
             at /usr/src/lib/libthr/thread/thr_exit.c:290:3
  15: thread_unwind_stop
             at /usr/src/lib/libthr/thread/thr_exit.c:162:3
  16: unwind_phase2_forced
             at /usr/src/contrib/llvm-project/libunwind/src/UnwindLevel1.c:337:3
  17: _Unwind_ForcedUnwind
             at /usr/src/contrib/llvm-project/libunwind/src/UnwindLevel1.c:420:10
  18: _Unwind_ForcedUnwind
             at /usr/src/lib/libthr/thread/thr_exit.c:109:9
  19: thread_unwind
             at /usr/src/lib/libthr/thread/thr_exit.c:175:2
  20: _pthread_exit_mask
             at /usr/src/lib/libthr/thread/thr_exit.c:257:3
  21: _Tthr_exit
             at /usr/src/lib/libthr/thread/thr_exit.c:209:2
  22: thread_start
             at /usr/src/lib/libthr/thread/thr_create.c:292:2
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5
error: test failed, to rerun pass `--lib`

Caused by:
  process didn't exit successfully: `/tmp/cirrus-ci-build/target/debug/deps/maturin-9ead281c73b2735c` (signal: 6, SIGABRT: process abort signal)

Exit status: 101

See also https://cirrus-ci.com/task/6008761958006784

THREAD_ID_MANAGER.lock().unwrap().free(thread.id);

T:Send (again)

Hi,

I have a type which is strictly !Send + !Sync. The destructor can only be called from the thread it's created on (which must be the main thread - although I can implement this myself).

I can't use thread_local b/c it requires Sync - is there any way round this?

Cheers,
James

Tests occasionally fail (Apple M2 Max)

On an M2 Max MacBook Pro, I am experiencing unexpected causality issues tied to using ThreadLocal. I ran the library's tests to see if I could exhibit similar behavior, given that the iter() test in particular looks like a decent model of my real-world situation, and indeed I get occasional test failures within seconds of repeatedly running the test suite. Here are a few:

running 6 tests
test thread_id::test_thread ... ok
test tests::same_thread ... ok
test tests::is_sync ... ok
test tests::different_thread ... ok
test tests::test_drop ... FAILED
test tests::iter ... ok

failures:

---- tests::test_drop stdout ----
thread 'tests::test_drop' panicked at 'assertion failed: `(left == right)`
  left: `0`,
 right: `1`', src/lib.rs:641:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
running 6 tests
test tests::test_drop ... ok
test thread_id::test_thread ... ok
test tests::is_sync ... ok
test tests::same_thread ... ok
test tests::different_thread ... ok
test tests::iter ... FAILED

failures:

---- tests::iter stdout ----
thread 'tests::iter' panicked at 'assertion failed: `(left == right)`
  left: `[1, 2, 3]`,
 right: `[1]`', src/lib.rs:616:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
running 6 tests
test tests::is_sync ... ok
test thread_id::test_thread ... ok
test tests::test_drop ... ok
test tests::same_thread ... ok
test tests::different_thread ... ok
test tests::iter ... FAILED

failures:

---- tests::iter stdout ----
thread 'tests::iter' panicked at 'assertion failed: `(left == right)`
  left: `[1, 2, 3]`,
 right: `[1, 3]`', src/lib.rs:616:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
running 6 tests
test tests::is_sync ... ok
test thread_id::test_thread ... ok
test tests::test_drop ... ok
test tests::same_thread ... ok
test tests::different_thread ... ok
test tests::iter ... FAILED

failures:

---- tests::iter stdout ----
thread 'tests::iter' panicked at 'assertion failed: `(left == right)`
  left: `[1, 2, 3]`,
 right: `[2, 3]`', src/lib.rs:616:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Note that exhibiting failures is much harder if you don't run the complete test suite, so I suspect the issue has something to do with threads being reused.

Data race in `RawIter::next`

I have a program where one thread iterates through thread-locals and another thread calls get_or_default(). Running under Miri results in:

error: Undefined Behavior: Data race detected between Atomic Load on Thread(id = 0, name = "main") and Write on Thread(id = 1) at alloc378305+0x30 (current vector clock = VClock([44]), conflicting timestamp = VClock([0, 26]))
    --> /home/ibraheem/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/sync/atomic.rs:2363:24
     |
2363 |             Acquire => intrinsics::atomic_load_acq(dst),
     |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between Atomic Load on Thread(id = 0, name = "main") and Write on Thread(id = 1) at alloc378305+0x30 (current vector clock = VClock([44]), conflicting timestamp = VClock([0, 26]))
     |
     = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
     = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

     = note: inside `std::sync::atomic::atomic_load::<u8>` at /home/ibraheem/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/sync/atomic.rs:2363:24
     = note: inside `std::sync::atomic::AtomicBool::load` at /home/ibraheem/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/sync/atomic.rs:385:18
     = note: inside `thread_local::RawIter::next::<crystalline::raw::Slots<3_usize>>` at /home/ibraheem/.cargo/registry/src/github.com-1ecc6299db9ec823/thread_local-1.1.3/src/lib.rs:387:24

It looks like the issue is here. I don't have a minimal reproduction right now, but I'll see if I can make one.

`miri` reports leaking memory with 1.1.5

Hello 👋🏻

With the latest release of thread_local, miri reports a memory leak. I tracked this down to the version bump of thread_local v1.1.5.

This is the output of miri:

     Running tests/test_span_trace.rs (target/miri/x86_64-unknown-linux-gnu/debug/deps/test_span_trace-315bdddcdb7b9126)

running 2 tests
test captured ... ok
test provided ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

The following memory was leaked: alloc281964 (Rust heap, size: 64, align: 8) {
    0x00 │ 01 00 00 00 00 00 00 00 00 __ __ __ __ __ __ __ │ .........░░░░░░░
    0x10 │ 02 00 00 00 00 00 00 00 00 __ __ __ __ __ __ __ │ .........░░░░░░░
    0x20 │ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ ░░░░░░░░░░░░░░░░
    0x30 │ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ │ ░░░░░░░░░░░░░░░░
}

error: the evaluated program leaked memory

note: pass `-Zmiri-ignore-leaks` to disable this check

error: aborting due to previous error

Will this crate's MSRV bump past Debian Stable?

This is a nice crate and it may be useful in the async-executor crate. However async-executor follows a strict Debian Stable-based MSRV policy. Do you foresee a future where the MSRV for this crate is bumped past Debian Stable? (Currently 1.63)

Undefined behavior when getting a value twice, and from another thread in between

The following program:

let tls = std::sync::Arc::new(ThreadLocal::new());

let s = tls.get_or(|| "Hello World".to_owned());

let thread_tls = tls.clone();
std::thread::spawn(move || {
    thread_tls.get_or(String::new);
})
.join()
.unwrap();

tls.get().unwrap();

s;

Produces undefined behavior when run through Miri:

error: Undefined Behavior: trying to reborrow for SharedReadOnly, but parent tag <234426> does not have an appropriate item in the borrow stack
   --> src/main.rs:17:5
   |
17 |     s;
   |     ^ trying to reborrow for SharedReadOnly, but parent tag <234426> does not have an appropriate item in the borrow stack
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information

I think this is caused by moving the data to the current hash map in the get_slow path. If I do not move it to the current hash map and instead just get it directly, this UB does not occur.

`get_slow` with `THREAD_GUARD` can trigger: cannot access a Thread Local Storage value during or after destruction: AccessError

I'm trying to get to the bottom of a weird crash I'm getting. I have minimal reproducible here:

https://github.com/gerwin3/thread-local-accesserror-repro/blob/main/src/main.rs

System:

  • Arch Linux x86_64 6.7.6-arch1-1
  • AMD Ryzen 9 5900X

To trigger the crash: close the window with CTRL + W.

The app uses egui and tracing-appender and the combination somehow triggers an AccessError (cannot access a Thread Local Storage value during or after destruction: AccessError) that eventually is caused by a function get_slow in thread_local:

THREAD_GUARD.with(|guard| guard.id.set(new.id));

My guess is that calling with on a thread local may not always be correct if the caller is running a destructor (?) in any case the docs of LocalKey::with seem to suggest so: https://doc.rust-lang.org/std/thread/struct.LocalKey.html#method.with

Not sure if I'm on the right track here. But since the documentation on this crate does not seem to suggest that ThreadLocal::get can panic in certain situations, and it seems it can actually panic in this case it seems this is a bug.

Why is T required to be Send?

Why is T required to be Send if the wrapped value is only accessible within the same thread it has been created from?

pub struct ThreadLocal<T: ?Sized + Send> { /* fields omitted */ }

MSRV is incorrectly set to 1.60

Running clippy on nightly triggers the incompatible_msrv, which highlights that async/await only became available in Rust 1.64:

warning: current MSRV (Minimum Supported Rust Version) is `1.60.0` but this item is stable since `1.64.0`
   --> src\lib.rs:160:20
    |
160 |             future.await
    |                    ^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
    = note: `#[warn(clippy::incompatible_msrv)]` on by default

This crate's MSRV might need to be bumped.

1.1.5 doesn't compile on Rust 1.57

Attempting to compile thread local on Rust 1.57 results in the following compiler error:

error[E0658]: use of unstable library feature 'thread_local_const_init'
   --> /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/thread_local-1.1.5/src/thread_id.rs:130:9
    |
130 |         thread_local! { static THREAD: Cell<Option<Thread>> = const { Cell::new(None) }; }
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #84223 <https://github.com/rust-lang/rust/issues/84223> for more information
    = note: this error originates in the macro `$crate::__thread_local_inner` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0658`.
error: could not compile `thread_local` due to previous error

Not sure if this was intentional change to the MSRV or not. The README lists the MSRV as 1.36.0.

Miri reported undefined behavior when using `ThreadLocal::iter` concurrently from multiple threads.

Miri seems to report undefined behavior when using ThreadLocal::iter from multiple threads concurrently.

The code that generated this: https://github.com/james7132/async-executor/blob/master/src/lib.rs#L805. I'll try to make a minimal repro sometime soon.

Full miri log trace.

error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `<unnamed>` and (2) atomic load on thread `<unnamed>` at alloc3225. (2) just happened here
   --> /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/thread_local-1.1.7/src/lib.rs:391:24
    |
391 |                     if entry.present.load(Ordering::Acquire) {
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data race detected between (1) non-atomic read on thread `<unnamed>` and (2) atomic load on thread `<unnamed>` at alloc3225. (2) just happened here
    |
help: and (1) occurred earlier here
   --> src/lib.rs:74:21
    |
15  |     .each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = help: overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only
    = help: see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span):
    = note: inside `thread_local::RawIter::next::<async_executor::LocalQueue>` at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/thread_local-1.1.7/src/lib.rs:391:24: 391:61
    = note: inside `<thread_local::Iter<'_, async_executor::LocalQueue> as std::iter::Iterator>::next` at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/thread_local-1.1.7/src/lib.rs:459:9: 459:41
    = note: inside `<thread_local::Iter<'_, async_executor::LocalQueue> as std::iter::Iterator>::fold::<usize, {closure@<thread_local::Iter<'_, async_executor::LocalQueue> as std::iter::Iterator>::count::{closure#0}}>` at /home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:2617:29: 2617:40
    = note: inside `<thread_local::Iter<'_, async_executor::LocalQueue> as std::iter::Iterator>::count` at /home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:264:9: 268:10
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:798:25: 798:52
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:705:23: 705:31
    = note: inside `<futures_lite::future::PollFn<{closure@async_executor::Ticker<'_>::runnable_with<{closure@async_executor::Runner<'_>::runnable::{closure#0}::{closure#0}}>::{closure#0}::{closure#0}}> as futures_lite::Future>::poll` at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:200:9: 200:21
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:726:10: 726:15
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:819:14: 819:19
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:255:62: 255:67
    = note: inside `<futures_lite::future::Or<async_channel::Recv<'_, ()>, {async block@async_executor::Executor<'_>::run<std::result::Result<(), async_channel::RecvError>, async_channel::Recv<'_, ()>>::{closure#0}::{closure#0}}> as futures_lite::Future>::poll` at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:449:33: 449:54
    = note: inside closure at /home/runner/work/async-executor/async-executor/src/lib.rs:263:32: 263:37
    = note: inside closure at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:99:19: 99:43
error: doctest failed, to rerun pass `--doc`
    = note: inside `std::thread::LocalKey::<std::cell::RefCell<(parking::Parker, std::task::Waker)>>::try_with::<{closure@futures_lite::future::block_on<std::result::Result<(), async_channel::RecvError>, {async fn body@async_executor::Executor<'_>::run<std::result::Result<(), async_channel::RecvError>, async_channel::Recv<'_, ()>>::{closure#0}}>::{closure#0}}, std::result::Result<(), async_channel::RecvError>>` at /home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:286:16: 286:31
    = note: inside `std::thread::LocalKey::<std::cell::RefCell<(parking::Parker, std::task::Waker)>>::with::<{closure@futures_lite::future::block_on<std::result::Result<(), async_channel::RecvError>, {async fn body@async_executor::Executor<'_>::run<std::result::Result<(), async_channel::RecvError>, async_channel::Recv<'_, ()>>::{closure#0}}>::{closure#0}}, std::result::Result<(), async_channel::RecvError>>` at /home/runner/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:262:9: 262:25
    = note: inside `futures_lite::future::block_on::<std::result::Result<(), async_channel::RecvError>, {async fn body@async_executor::Executor<'_>::run<std::result::Result<(), async_channel::RecvError>, async_channel::Recv<'_, ()>>::{closure#0}}>` at /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-lite-2.2.0/src/future.rs:78:5: 104:7
note: inside closure
   --> src/lib.rs:74:21
    |
15  |     .each(0..4, |_| future::block_on(ex.run(shutdown.recv())))
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.