Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialization of CONTEXT in timestamp::context::shared_context() not thread-safe #730

Open
tpunder opened this issue Jan 21, 2024 · 1 comment

Comments

@tpunder
Copy link

tpunder commented Jan 21, 2024

The initialization of CONTEXT in shared_context() is not thread-safe. Multiple threads calling shared_context() can end up using an un-initialized CONTEXT before the thread that successfully calls compare_exchange can fully initialize CONTEXT.

uuid/src/timestamp.rs

Lines 372 to 392 in cefc353

#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
static CONTEXT: Context = Context {
count: Atomic::new(0),
};
#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
static CONTEXT_INITIALIZED: Atomic<bool> = Atomic::new(false);
#[cfg(all(any(feature = "v1", feature = "v6"), feature = "std", feature = "rng"))]
pub(crate) fn shared_context() -> &'static Context {
// If the context is in its initial state then assign it to a random value
// It doesn't matter if multiple threads observe `false` here and initialize the context
if CONTEXT_INITIALIZED
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
CONTEXT.count.store(crate::rng::u16(), Ordering::Release);
}
&CONTEXT
}

Here is an example that demonstrates the problem:

use std::thread;
use uuid::Uuid;

fn main() {
    let threads: Vec<thread::JoinHandle<()>> = (0..10).map(|i| {
        thread::spawn(move || {
            let node_id: [u8; 6] = [ 1, 2, 3, 4, 5, 6 ];
            let uuid = Uuid::now_v6(&node_id);
            let counter = uuid.get_timestamp().unwrap().to_rfc4122().1;
            println!("Hello from thread {i} - uuid: {uuid} - counter: {counter}!");
        })
    }).collect();

    threads.into_iter().for_each(|t| t.join().unwrap() );
}

Here are a few runs from an Macbook Pro M1 (arm64). You can see some of the threads are starting from the un-initialized value of 0 and counting from there before the thread that successfully runs the compare_exchange is able to actually initialized CONTEXT with a random value:

» cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 2 - uuid: 1eeb8a59-b9dc-66ca-8000-010203040506 - counter: 0!
Hello from thread 1 - uuid: 1eeb8a59-b9dc-6864-8002-010203040506 - counter: 2!
Hello from thread 3 - uuid: 1eeb8a59-b9dc-67c4-8001-010203040506 - counter: 1!
Hello from thread 0 - uuid: 1eeb8a59-b9dc-6968-87f3-010203040506 - counter: 2035!
Hello from thread 4 - uuid: 1eeb8a59-b9dc-6b52-87f4-010203040506 - counter: 2036!
Hello from thread 6 - uuid: 1eeb8a59-b9dc-6b7a-87f5-010203040506 - counter: 2037!
Hello from thread 5 - uuid: 1eeb8a59-b9dc-6c60-87f6-010203040506 - counter: 2038!
Hello from thread 7 - uuid: 1eeb8a59-b9dc-6daa-87f7-010203040506 - counter: 2039!
Hello from thread 8 - uuid: 1eeb8a59-b9dc-6e86-87f8-010203040506 - counter: 2040!
Hello from thread 9 - uuid: 1eeb8a59-b9dc-6f6c-87f9-010203040506 - counter: 2041!

» cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 2 - uuid: 1eeb8a59-c4ae-617a-8000-010203040506 - counter: 0!
Hello from thread 1 - uuid: 1eeb8a59-c4ae-61c0-8001-010203040506 - counter: 1!
Hello from thread 4 - uuid: 1eeb8a59-c4ae-642c-8002-010203040506 - counter: 2!
Hello from thread 3 - uuid: 1eeb8a59-c4ae-6440-8003-010203040506 - counter: 3!
Hello from thread 5 - uuid: 1eeb8a59-c4ae-64a4-8004-010203040506 - counter: 4!
Hello from thread 0 - uuid: 1eeb8a59-c4ae-65ee-8e52-010203040506 - counter: 3666!
Hello from thread 7 - uuid: 1eeb8a59-c4ae-6936-8e53-010203040506 - counter: 3667!
Hello from thread 6 - uuid: 1eeb8a59-c4ae-6936-8e54-010203040506 - counter: 3668!
Hello from thread 8 - uuid: 1eeb8a59-c4ae-6ada-8e55-010203040506 - counter: 3669!
Hello from thread 9 - uuid: 1eeb8a59-c4ae-6bfc-8e56-010203040506 - counter: 3670!

» cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 1 - uuid: 1eeb8a59-e1dc-6918-8000-010203040506 - counter: 0!
Hello from thread 2 - uuid: 1eeb8a59-e1dc-6af8-8002-010203040506 - counter: 2!
Hello from thread 4 - uuid: 1eeb8a59-e1dc-6dbe-8003-010203040506 - counter: 3!
Hello from thread 5 - uuid: 1eeb8a59-e1dc-6e2c-8004-010203040506 - counter: 4!
Hello from thread 3 - uuid: 1eeb8a59-e1dc-6af8-8001-010203040506 - counter: 1!
Hello from thread 6 - uuid: 1eeb8a59-e1dd-61ce-8005-010203040506 - counter: 5!
Hello from thread 7 - uuid: 1eeb8a59-e1dd-625a-8006-010203040506 - counter: 6!
Hello from thread 8 - uuid: 1eeb8a59-e1dd-6304-8007-010203040506 - counter: 7!
Hello from thread 0 - uuid: 1eeb8a59-e1dd-6372-a008-010203040506 - counter: 8200!
Hello from thread 9 - uuid: 1eeb8a59-e1dd-639a-a009-010203040506 - counter: 8201!

Here are some runs from an Intel Core i7 (x86_64):

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 3 - uuid: 1eeb8a89-27dc-6f82-8000-010203040506 - counter: 0!
Hello from thread 1 - uuid: 1eeb8a89-27dd-6060-8002-010203040506 - counter: 2!
Hello from thread 0 - uuid: 1eeb8a89-27dc-6f81-8001-010203040506 - counter: 1!
Hello from thread 4 - uuid: 1eeb8a89-27dd-66b4-8003-010203040506 - counter: 3!
Hello from thread 5 - uuid: 1eeb8a89-27dd-6c9b-8004-010203040506 - counter: 4!
Hello from thread 2 - uuid: 1eeb8a89-27de-604a-a68e-010203040506 - counter: 9870!
Hello from thread 7 - uuid: 1eeb8a89-27de-61e2-a68f-010203040506 - counter: 9871!
Hello from thread 6 - uuid: 1eeb8a89-27de-63e0-a690-010203040506 - counter: 9872!
Hello from thread 8 - uuid: 1eeb8a89-27de-6485-a691-010203040506 - counter: 9873!
Hello from thread 9 - uuid: 1eeb8a89-27de-674c-a692-010203040506 - counter: 9874!

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 2 - uuid: 1eeb8a89-904f-6bd7-8001-010203040506 - counter: 1!
Hello from thread 1 - uuid: 1eeb8a89-904f-6bd5-8000-010203040506 - counter: 0!
Hello from thread 3 - uuid: 1eeb8a89-904f-6be2-8003-010203040506 - counter: 3!
Hello from thread 4 - uuid: 1eeb8a89-904f-6bd5-8002-010203040506 - counter: 2!
Hello from thread 5 - uuid: 1eeb8a89-9050-6809-8004-010203040506 - counter: 4!
Hello from thread 0 - uuid: 1eeb8a89-9050-6b45-b4cd-010203040506 - counter: 13517!
Hello from thread 6 - uuid: 1eeb8a89-9051-6243-b4ce-010203040506 - counter: 13518!
Hello from thread 8 - uuid: 1eeb8a89-9051-648c-b4cf-010203040506 - counter: 13519!
Hello from thread 7 - uuid: 1eeb8a89-9051-651d-b4d0-010203040506 - counter: 13520!
Hello from thread 9 - uuid: 1eeb8a89-9051-67fa-b4d1-010203040506 - counter: 13521!

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/weather`
Hello from thread 1 - uuid: 1eeb8a89-ff0e-6a0d-8001-010203040506 - counter: 1!
Hello from thread 2 - uuid: 1eeb8a89-ff0e-6a0d-8002-010203040506 - counter: 2!
Hello from thread 3 - uuid: 1eeb8a89-ff0e-6a0b-8000-010203040506 - counter: 0!
Hello from thread 4 - uuid: 1eeb8a89-ff0f-60ac-8003-010203040506 - counter: 3!
Hello from thread 5 - uuid: 1eeb8a89-ff0f-65aa-8004-010203040506 - counter: 4!
Hello from thread 0 - uuid: 1eeb8a89-ff0f-671d-9d90-010203040506 - counter: 7568!
Hello from thread 6 - uuid: 1eeb8a89-ff0f-6999-9d91-010203040506 - counter: 7569!
Hello from thread 9 - uuid: 1eeb8a89-ff0f-6cc4-9d92-010203040506 - counter: 7570!
Hello from thread 7 - uuid: 1eeb8a89-ff0f-6ddf-9d93-010203040506 - counter: 7571!
Hello from thread 8 - uuid: 1eeb8a89-ff0f-6fdb-9d94-010203040506 - counter: 7572!
@KodrAus
Copy link
Member

KodrAus commented Jan 28, 2024

Thanks for pointing this out @tpunder! I think the important thing is for each UUID generated sequentially within the same timestamp interval (100ns for v1/v6 and 1ms for v7) to get a counter value that is larger than the previous. This is technically breakable under this scheme, but extremely unlikely. For v7 UUIDs it's recommended we randomize the counter each millisecond. I'm not sure if we'll want to do that, but should come up with some scheme to occasionally shuffle the counter.

So I think there is an issue here we need to fix. The solution to it will probably be whatever we come up with for v7 UUIDs using counters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants