-
Notifications
You must be signed in to change notification settings - Fork 36
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
std::sync::Mutex
using batch semaphore
#155
Conversation
// latter case, we check the state to report a more precise | ||
// error message. | ||
state = self.state.borrow_mut(); | ||
if let Some(holder) = state.holder { |
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 this is None
, then we have a bug in the Mutex
– swap to state.holder.expect(...)
?
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 think this is the case: state.holder
may still be None
because there is a yield between the acquiry of a permit from the semaphore and the actual state update (the two are not performed in a single atomic step).
However, the check is still sufficient here because in the case of a thread attempting to re-acquire a lock it already holds, it must have returned from the first lock
call by the time the second one is checking the state.
src/future/batch_semaphore.rs
Outdated
/// the permits remaining in the semaphore. | ||
fn reblock_if_unfair(&self) { | ||
let state = self.state.borrow_mut(); | ||
if state.fairness == Fairness::Unfair { |
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: We're not planning to support changing the fairness once a semaphore has been created. Given that, I wonder if we could just put the fairness
field outside the RefCell
so we can do this check before calling borrow_mut
?
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, resolved in the new commit, though fairness
needed to be added to BatchSemaphoreState::acquire_permits
here, which is maybe not great.
// acquires may succeed, as long as the requesters were not | ||
// blocking on the semaphore at the time of the panic. This is | ||
// used to correctly model lock poisoning. | ||
state.permits_available.release(num_permits, VectorClock::new()); |
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 VectorClock::new()
here, instead of the current caller's clock?
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.
When this is done as part of Drop
handlers, current::clock
may panic. I think this is the same thing as mentioned here for the close
clock: the way execution stops needs to be changed a bit.
self.semaphore.reblock_if_unfair(); | ||
|
||
// Yield so other threads can fail a `try_acquire`. | ||
thread::switch(); |
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.
[Aside] Even though we're always calling thread::switch()
after reblock_if_unfair
, I think it's better not to push the switch inside the reblock, and keep them outside, as you have done here.
if let Some(holder) = state.holder { | ||
if holder == me { | ||
panic!("deadlock! task {:?} tried to acquire a Mutex it already holds", me); | ||
if !self.semaphore.is_closed() { |
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.
Feels a bit strange that you're checking if the semaphore is closed here, but then you call unwrap
on the acquire call on line 70. If we're not expecting the semaphore to be closed, maybe just assert that here?
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 is the other way though: if it is not closed, then we can unwrap
the acquire_blocking
call. Otherwise, we will continue and should get a poisoned lock from the inner mutex.
Reimplements
std::sync::Mutex
using batch semaphore. The tests already include cases to check causal dependencies and the number of context switches, so these have not changed.This PR changes
block_on
to not yield before the future is polled for the first time. This caused one replay test to fail. I'm not sure if we should bump versions for this, since it is technically a breaking change for schedules?We can delay this PR until after we start the crate reorganisation, but this is ready for reviews in any case.
While trying to make sure the new semaphore implementation is equivalent to the old one in terms of context switches, I found it useful to write out how the methods work at a high-level, including their yield points. Including this here in case it's useful:
Mutex::lock
when blockedavailable_permits
(0 available)BatchSemaphore::acquire_blocking(1)
blockedblock_on
poll yield; wait until woken)try_acquire
)acquire_blocking
returns)Mutex::lock
when not blockedavailable_permits
(1)BatchSemaphore::acquire_blocking(1)
not blockedtry_acquire
)acquire_blocking
returns)Mutex::try_lock
failedBatchSemaphore::try_acquire(1)
failedtry_acquire
in this thread may succeed)try_acquire
returns)Mutex::try_lock
successfulBatchSemaphore::try_acquire(1)
successfultry_acquire
)try_acquire
returns)Mutex::drop
BatchSemaphore::release(1)
release
returns)Mutex::drop
when panickingBatchSemaphore::release(1)
release
returns)By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.