-
Notifications
You must be signed in to change notification settings - Fork 217
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
Safer word lock #108
Safer word lock #108
Conversation
There is a good reason for using |
core/src/word_lock.rs
Outdated
fn is_locked(&self) -> bool; | ||
fn is_queue_locked(&self) -> bool; | ||
fn queue_head(&self) -> *const ThreadData; | ||
fn set_queue_head(&self, thread_data: *const ThreadData) -> Self; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't modify self
, so maybe with_queue_head
is a better name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. Will also make every method consume self
since it's just a Copy
type anyway.
Ah, crap. I suspected that was the reason. So I will make |
c4f0ee2
to
f164c81
Compare
a761406
to
96a7d5b
Compare
core/src/word_lock.rs
Outdated
} | ||
|
||
// If ThreadData is expensive to construct, then we want to use a cached | ||
// version in thread-local storage if possible. | ||
if !cfg!(windows) && !cfg!(all(feature = "nightly", target_os = "linux")) { | ||
thread_local!(static THREAD_DATA: ThreadData = ThreadData::new()); | ||
if let Some(tls) = try_get_tls(&THREAD_DATA) { | ||
return &*tls; | ||
if let Some(result) = try_with_tls(&THREAD_DATA, &mut f) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so this is a bit subtle, but I would prefer if f
was only called from one place and you simply extracted a reference from the thread-local storage. The reason for this is that if f
is used in 2 different places, it will effectively be inlined twice into the function, which doubles the code size. This will require a bit of unsafe code to juggle the lifetimes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah. Makes sense. I did this now, but without much unsafe. Instead of juggling too many pointers I just stored the reference or owned value in a Cow<ThreadData>
ff6ae74
to
69372b7
Compare
core/src/word_lock.rs
Outdated
if thread_data_ptr.is_null() { | ||
// Otherwise just create a ThreadData on the stack | ||
thread_data_storage = Some(ThreadData::new()); | ||
thread_data_ptr = thread_data_storage.as_ref().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use Option::get_or_insert_with
instead of this if
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how that would work here. thread_data_storage
is always None
when reaching this if block. So the closure to get_or_insert_with
would always run and I would still need to get the address out for my pointer variable later. My initial declaration of thread_data_storage
was a bit off, but I moved it now to better show how it's used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thread_data_ptr = thread_data_storage.get_or_insert_with(ThreadData::new);
Since get_or_insert_with
returns a reference to the inner value in the option, you can avoid calling as_ref
and unwrap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂️ I completely missed it returns a reference. Of course it does.
a5d45ec
to
afa5b28
Compare
afa5b28
to
c139320
Compare
Thanks! |
I tried to reason a bit about the invariants of the unsafe code in WordLock. I think we can get away with them not being
unsafe
with some minor adjustments. Mostly this PR removesunsafe
keywords from methods and addsunsafe
blocks within them. But there is one more difference that I'm hoping should make this safe to call from the outside:Switching from
fetch_sub
tofetch_and
is intended to make the method not corruptself.state
if called on an already unlockedWordLock
. Previously multiple calls would continue to decrement the integer by one, locking and unlocking it as well as corrupting the part of it that is used as a pointer. By using the bit and operation this should just zero out theLOCKED_BIT
without touching the rest. The test suite passes, I'm not sure if I have missed something else that makes this a bad change.If you don't like the trait on
usize
providing the convenience methods we can skip them. They are unrelated to making the methods safe, other than it might be harder to screw code up if the bit manipulation is extracted.