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

Rewrite RwLock to use folly upgradeable implementation #66

Merged
merged 7 commits into from
Aug 27, 2019
Merged

Rewrite RwLock to use folly upgradeable implementation #66

merged 7 commits into from
Aug 27, 2019

Conversation

64
Copy link
Contributor

@64 64 commented Jun 28, 2019

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:

$ cargo run --bin rwlock --release 1 3 100 10000 10 5
spin::RwLock (new)   - [write]     43.180 kHz [read]    129.347 kHz
spin::RwLock (old)   - [write]     43.146 kHz [read]    129.297 kHz

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.

@64
Copy link
Contributor Author

64 commented Jun 28, 2019

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;
Copy link
Contributor

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.

@Ericson2314
Copy link
Contributor

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)
Copy link
Contributor

@Ericson2314 Ericson2314 Jun 29, 2019

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.

Copy link
Contributor Author

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)?

Copy link
Contributor

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> {
Copy link
Contributor

@Ericson2314 Ericson2314 Jun 29, 2019

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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

@64
Copy link
Contributor Author

64 commented Jun 30, 2019

side note: how would you feel about marking RAII guards as must_use? because otherwise they just unlock instantly.

@Ericson2314
Copy link
Contributor

Oh sorry, I meant to approve of that, but fat-fingered it on my phone.

@Ericson2314
Copy link
Contributor

Also keep in mind while I wrote the original RwLock, @mvdnes still needs to sign off on everything and alone can merge.

@64
Copy link
Contributor Author

64 commented Jul 4, 2019

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).

@vinaychandra
Copy link

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).

@64
Copy link
Contributor Author

64 commented Jul 9, 2019

@vinaychandra How's that?

@vinaychandra
Copy link

vinaychandra commented Jul 9, 2019

For example, this is the implementation.

Quoting

Note that the unfairness here is extreme: if the lock is
continually accessed for read, writers will never get a chance

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,

RWTicketSpinLock shows more balanced READ/WRITE performance.  If
 *  your application really needs a lot more threads, and a
 *  higher-priority writer, prefer one of the RWTicketSpinLock locks.

@64
Copy link
Contributor Author

64 commented Jul 9, 2019

@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?

@64
Copy link
Contributor Author

64 commented Aug 23, 2019

@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.

@mvdnes
Copy link
Owner

mvdnes commented Aug 24, 2019

Sorry for the absence.
This crate has grown quite a bit since it's original conception. Unfortunately I don't have the time and motivation to fully understand these changes.
If you can vouch for this PR than I will merge it, as I do understand it's importance.

@xacrimon
Copy link

Hello @mvdnes. Friend of Matt and serious lockfree dude here. I have read thru the changes and tested extensively. It looks good to me.

@mvdnes mvdnes merged commit 33b18f4 into mvdnes:master Aug 27, 2019
@mvdnes
Copy link
Owner

mvdnes commented Aug 27, 2019

Ok then. Merged!

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

Successfully merging this pull request may close these issues.

Wrong atomic ordering in Drop impl for RwLockWriteGuard and buggy lock acquisition failure in RwLock::try_read
5 participants