-
Notifications
You must be signed in to change notification settings - Fork 93
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
Rewrite RwLock to use folly upgradeable implementation #66
Conversation
I think I can improve the API with a bit of unsafe magic. Specifically I’d like to move the upgrade functions onto the RAII guard itself. Will try tomorrow |
src/rw_lock.rs
Outdated
lock: AtomicUsize, | ||
data: UnsafeCell<T>, | ||
} | ||
|
||
const READER: usize = 4; |
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: I would use 1 << 2
here for clarity.
Yeah this looks pretty good overall, thanks for doing this! |
src/rw_lock.rs
Outdated
// uses compare_exchange (strong) internally. | ||
if self | ||
.lock | ||
.compare_exchange_weak(0, WRITER, Ordering::Acquire, Ordering::Relaxed) |
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.
Can we use a higher order function to abstract over compare_exchange_weak
vs compare_exchange
? #[inline_always]
it just to make sure it's compiled away.
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.
hm, you mean like some free function fn compare_exchange(current: usize, new: usize, success: Ordering, failure: Ordering, strong: bool)
?
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 was thinking like
fn make_try_write(
&self,
comparer: fn(&AtomicUSize, isize, isize, Ordering, failure: Ordering) -> Result<isize, isize>
) -> ...
but yeah yours works too.
/// assert!(mylock.try_read().is_some()); | ||
/// assert_eq!(*readable, 1); | ||
/// ``` | ||
pub fn downgrade(self) -> RwLockReadGuard<'rwlock, 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.
I feel like it might be possible to optimize this? What about:
let old = self.lock.load(Ordering::Relaxed);
self.lock.store((old & !UPGRADED) + READER, Ordering::Release);
let new_guard = ...;
mem::forget(self);
new_guard
and same for write lock downgrade. Since the readers cannot change while a write or upgradable lock is held, there is no race condition between the load and store, and since the downgrade is conceptually a partial release of the lock there is no unnecessary acquiring.
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.
Readers count can change at any time, because acquiring the read lock uses fetch_add and then checks if it was valid and undoes if necessary.
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 true. There might still be a way to optimize it; maybe self.lock.fetch_add(READER, Ordering::Relaxed);
, but we can worry about that later.
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.
yeah it's possible that relaxed ordering is fine there. shouldn't be a problem as long as the add happens before the drop, and the drop happens before the user can access the returned guard. i'll fix the other issue and push later today
side note: how would you feel about marking RAII guards as must_use? because otherwise they just unlock instantly. |
Oh sorry, I meant to approve of that, but fat-fingered it on my phone. |
Also keep in mind while I wrote the original RwLock, @mvdnes still needs to sign off on everything and alone can merge. |
I would strongly recommend yanking previous versions of the crate which contain the bug (which looks to be every version of the crate for the last 5 years, unfortunately). |
Hi @64 can you please add a few lines in the document commenting about writer contention? Each of the multiple implementations of spinlock in folly have their own considerations (some can starve writers to an extreme for example). |
@vinaychandra How's that? |
For example, this is the implementation. Quoting
It would be great if we had an option to choose how writer contentions work. We can tweak it so that we have no writer starvation (or atleast reduce the extreme case). (See the other implementations in folly for example) There is also RWTicketSpinLock which is a bit more fair but slightly less READ performance Quoting,
|
@vinaychandra see my earlier commit. I agree it would be good to have a lock with better write performance. maybe you can submit a PR for that? |
@mvdnes I do apologise for nagging, but is there a chance you could take a look at this PR when you have time? It fixes some pretty dangerous UB in the current RwLock. |
Sorry for the absence. |
Hello @mvdnes. Friend of Matt and serious lockfree dude here. I have read thru the changes and tested extensively. It looks good to me. |
Ok then. Merged! |
Fixes #65.
I fully expect there to be some issues with this, so I'm going to get a few more eyes to look over it.
I've done a very quick performance test with the parking lot benchmarks:
I genuinely have no idea whether those arguments make for a good test lol. Happy to rerun with something else.
Also not sure how you want to handle semver here.