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

Guarantee uE implementors always get the correct UUID when calling UUIDBuilder::build() by using internally held singleton #74

Conversation

PLeVasseur
Copy link
Contributor

Addresses #58

What

Updated UUIDBuilder to expose only build() to allow end-users a simpler, more consistent API when generating UUIDs for their uE.

Why

End users of the library should be shielded from making mistakes, which is what we do here. They simply call UUIDBuilder::build() and they'll always get the correct result.

How

We use an internally held instance of UUIDBuilder which generates the rand_b portion once, meaning all calls to build() have the same rand_b in the UUIDs.

We use a compare-and-swap (CAS) technique using an AtomicU64 to ensure we correctly handled logic for generating timestamp + counter portion, even in extremely high-contention scenarios when generating UUIDs. To do so we had to refactor so that we regenerate the timestamp at the top of the CAS loop, rather than be passed in the timestamp to use.

Mutex vs Atomic Compare-and-Swap (CAS) Performance

Using the Atomic CAS technique is approximately twice as fast as using a Mutex

With 10 async tasks, each generating 400 UUIDs all released and run at once

  • Mutex: ~2ms
  • Atomic CAS: ~0.8ms

Copy link
Contributor

@evshary evshary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PLeVasseur I like your idea of using CAS. I left some nitpicks.

@@ -590,17 +593,22 @@ mod tests {
ttl,
..Default::default()
};

if let Some(created_millis_ago) = created_millis_ago {
thread::sleep(std::time::Duration::from_millis(created_millis_ago));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more intuitive to create a timeout UAttributes, but I'm not sure whether this will need a longer time to do the test. (Maybe it's ok while we don't have too many of this kind of tests).

src/uattributes/uattributesvalidator.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
UUID::from_u64_pair(new_msb, self.lsb)
.expect("should have been able to create UUID for valid timestamp")
// Try to update the MSB atomically.
match self.msb.compare_exchange(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so we use compare_exchange instead of swap here to ensure no one modifies the self.msb, right?
Brilliant idea!

src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@AnotherDaniel AnotherDaniel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, like what you can do with Rust to support the desired functionality, and that you put in the time to figure it out :)

src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
Comment on lines 597 to 672
thread::sleep(std::time::Duration::from_millis(created_millis_ago));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FMPOV it is an anti-pattern to rely on sleeping in test code. Please replace this with the original or a clock-tick based approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's bad to sleep in test code. Problem is that we now calculate the timestamp within the build_internal() function. Hmm. Do you have ideas on how to navigate the situation now that's the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK -- I think I was able to pull this off. Can you take another look @sophokles73?

src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
src/uuid/uuidbuilder.rs Outdated Show resolved Hide resolved
@PLeVasseur PLeVasseur force-pushed the feature/uuidbuilder_thread-safe_proposal branch from 12a72f2 to 9594a1a Compare April 2, 2024 15:45

let creation_time = now_millis_since_epoch_start - created_millis_ago;
UUIDBuilder::new().build_with_instant(creation_time)
fn uuid_n_ms_in_past(n_ms_in_past: u64) -> UUID {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add this as pub(crate) fn UUIDBuilder::build_with_instant(timestap) -> UUID so that it can (potentially) be reused in other test cases of the up-rust crate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see adding a pub(crate) function which does what I wrote here -- generates a UUID and then subtracts a certain amount of time from it.

I ran into issues with passing in an absolute timestamp. Read on to hear of my woes 👇


I was running into a fair amount of tricky debugging and headscratching before I moved away from the two-step process that was here of build() -> build_with_timestamp(), to calculating the timestamp purely in the loop of the compare-and-swap and not passing in the timestamp.

Imagine on thread 1 we had entered build_internal() from build_with_timestamp() with a timestamp of n and on thread 2 we had entered build_internal() from build_with_timestamp() with a timestamp of n+1, and the previous time in the msb` was n.

We make it to thread 2 first, see the msb time of n which differs from the passed in timestamp of n+1 and set the timestamp to n+1.

We hit thread 1 second, see the msb time of n+1 which differs from the passed in timestamp of n and set the timestamp to n (going back in time!).

If I missed the point though, please feel free to let me know your thoughts 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see adding a pub(crate) function which does what I wrote here -- generates a UUID and then subtracts a certain amount of time from it.

That is what I had in mind :-) Just didn't want to bury it in the test cases ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆 Can you tell from my paragraph that I had gone through some trauma...

Sounds good, I'll move this over into UUIDBuilder so it can be accessed from elsewhere for other tests 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey dokey -- can you take another look?

Comment on lines 21 to 22
pub(crate) const BITMASK_CLEAR_VERSION: u64 = 0xffff_ffff_ffff_0fff;
const BITMASK_CLEAR_VARIANT: u64 = 0x3fff_ffff_ffff_ffff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice formatting 👍

src/uuid/uuidbuilder.rs Show resolved Hide resolved
@PLeVasseur PLeVasseur force-pushed the feature/uuidbuilder_thread-safe_proposal branch from aa99f08 to ea89473 Compare April 3, 2024 11:15
Copy link
Contributor

@sophokles73 sophokles73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Thanks for contributing 👍

@sophokles73 sophokles73 merged commit 1ee4f8d into eclipse-uprotocol:main Apr 3, 2024
10 checks passed
@PLeVasseur PLeVasseur deleted the feature/uuidbuilder_thread-safe_proposal branch April 3, 2024 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants