-
Notifications
You must be signed in to change notification settings - Fork 107
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
Add support for RwLock #88
Conversation
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.
The implementation looks good to me, though I'd want to get @carllerche's opinion as well — I think he knows loom
's internals much better than I do.
I left comments on some minor nits.
src/rt/rwlock.rs
Outdated
seq_cst: bool, | ||
|
||
/// `Some` when the rwlock is in the locked state. | ||
/// The `thread::Id` references the thread that currently holds the rwlock. |
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 think this comment could be made clearer, since the lock
value could either be a single thread ID holding the write lock, or multiple thread IDs holding read locks.
execution.threads.seq_cst(); | ||
} | ||
|
||
// Block all other threads attempting to acquire rwlock |
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.
Nit/TIOLI: seems like this code is repeated enough that it could maybe be factored out into its own function...not a blocker.
src/rt/rwlock.rs
Outdated
|
||
impl RwLock { | ||
/// Common RwLock function | ||
pub(crate) fn new(seq_cst: bool) -> RwLock { |
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.
As far as I can tell, the rt::RwLock
type is only ever constructed in sync::RwLock::new
, and it always passes true
here. Will we ever construct a rt::RwLock
with seq_cst=false
? If not, should we just remove the bool and always establish sequential consistency?
#[derive(Debug)] | ||
pub struct RwLockReadGuard<'a, T> { | ||
lock: &'a RwLock<T>, | ||
data: Option<std::sync::RwLockReadGuard<'a, T>>, |
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.
It's not immediately obvious why this is an Option
. I think it's it so that the Drop
impl can drop the std
guard and release the std
lock before releasing the loom
mock lock, because that might cause another thread to acquire the lock?
I think a comment explaining this (either here on the field or in the Drop
impl) would be very helpful.
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.
Thanks for the feedback @hawkw. I'll get to these fixes ASAP!
I'm taking a second look at some of what I implemented. In trying to factor out some code I think I found a bug in my implementation. I'll remove the WIP title when this is ready for review again. |
836facf
to
0349e9b
Compare
This is ready for a final review... or at least I think it is right now... so throw your feedback at me (again)! Some feedback I haven't taken:
|
@pop re:
what do you think about something like this: // Set the lock to the current thread
let mut already_locked = false;
state.lock = match state.lock.take() {
None => {
let mut threads: HashSet<thread::Id> = HashSet::new();
threads.insert(thread_id);
Some(Locked::Read(threads))
}
Some(Locked::Read(threads)) => {
threads.insert(thread_id);
Some(Locked::Read(threads))
}
Some(Locked::Write(writer)) => {
already_locked = true;
Some(Locked::Write(writer))
}
};
if already_locked {
return false;
} |
pub(super) struct State { | ||
/// A single `thread::Id` when Write locked. | ||
/// A set of `thread::Id` when Read locked. | ||
lock: Option<Locked>, |
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.
Why does this have to be Option<Locked>
? Could it be Locked
and initialized at first use?
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.
If no threads are reading or writing the RwLock then it doesn't make sense for it to be in either Locked::Read
or Locked::Write
, so I think None
makes the most sense.
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, so similar to the following
#[derive(Debug, PartialEq)]
enum Locked {
Read(HashSet<thread::Id>),
Write(thread::Id),
None,
}
I guess Option<Locked>
makes more sense now but I wonder if the size is any different from Option<Locked>
compared to Locked
.
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.
Hmm. I don't know about that. I was more focused on the expressiveness of the implementation and not the size impact. I can change it to your suggestion if we're concerned about it. Your suggestion seems pretty expressive too -- "the RwLock can be in one of three states..." makes sense too.
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.
No, I am not sure whether it affects the size, just thought about it, we need to verify it with probably something like std::mem::size_of<Type>()
.
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.
Is this the kind of check you meant?
I added this print statement locally to rwlock's
new()
method:
println!("sizeof Option<Locked> {:?}", std::mem::size_of::<Option<Locked>>());
println!("sizeof Locked {:?}", std::mem::size_of::<Locked>());
and got this output:
sizeof Option<Locked> 64
sizeof Locked 64
Which makes me think both are smaller than the smallest memory allocation we can claim.
This was on Linux 5.2.18-200.fc30.x86_64
FWIW.
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.
Oh, I thought they have different sizes.
I think we're just waiting on a review from @carllerche, is that right @hawkw @pickfire? |
@pop as far as I know, that's right! This looks good to me! |
Yup, looks good to me too. |
Problem: `is_read_locked` and `is_write_locked` functions cause an "Already Borrowed" error. This implements a workaround, but it'd be good to have that sorted out before calling loom::rwlock "Done". Refs tokio-rs#21
Previously I was doing something like this: ``` state.lock = match &state.lock { None => { create and return new Read lock } Some(existing Read lock) => { Copy existing Read lock contents into new Read lock. Extend new Read lock and return that } ... } ``` This was not performant, but by using `&state.lock` we would have had to add lifetime specifiers _everywhere_. @hawkw shared with me the _very useful_ Option method `take()` which takes an Option's value and leaves None. This pretty much solved the problem: ``` state.lock = match state.lock.take() { None => { create and return new Read lock } Some(existing Read lock) { return extended read lock } ... } ``` Yay! Refs tokio-rs#21
I merged master and fixed the conflicts 👍 |
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.
LGTM, i'll merge once CI passes.
Sorry for the delay, thanks for doing the work! |
Btw, there is a loom channel on the Tokio discord (https://discord.gg/tokio). If something is blocked on me, feel free to ping me there. |
Fixes #21
Oh snap when did this move to the Tokio org? Well, all the same: I added support for RwLock!
I don't know if this is done, but the tests (which I got from the RwLock docs) pass so I'm happy.
I would love some feedback. The
is_read_locked()
andis_write_locked()
functions cause "Already Locked" panics so I am not using them at the moment. Let me know if you want more info on that.cc: @pickfire ?