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

Add Condvar APIs not susceptible to spurious wake #47970

Merged
merged 9 commits into from
Feb 25, 2018
Merged

Add Condvar APIs not susceptible to spurious wake #47970

merged 9 commits into from
Feb 25, 2018

Conversation

vlovich
Copy link

@vlovich vlovich commented Feb 2, 2018

Provide wait_until and wait_timeout_until helper wrappers that aren't susceptible to spurious wake.
Additionally wait_timeout_until makes it possible to more easily write code that waits for a fixed amount of time in face of spurious wakes since otherwise each user would have to do math on adjusting the duration.

Implements #47960.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @sfackler (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

Vitali Lovich added 2 commits February 2, 2018 11:29
Provide wait_until and wait_timeout_until helper wrappers that aren't
susceptible to spurious wake.
pub fn wait_until<'a, T, F>(&self, mut guard: MutexGuard<'a, T>,
mut condition: F)
-> LockResult<MutexGuard<'a, T>>
where F: FnMut(&T) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take &mut T.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

pub fn wait_timeout_until<'a, T, F>(&self, mut guard: MutexGuard<'a, T>,
mut dur: Duration, mut condition: F)
-> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)>
where F: FnMut(&T) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should take &mut T.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
let wait_timer = Instant::now();
let wait_result = self.wait_timeout(guard, dur)?;
dur = dur.checked_sub(wait_timer.elapsed()).unwrap_or(timed_out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait_result already contains the information about if we time out or not. It seems like we should use that rather than doubling the time manipulation work.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately that doesn't work because the semantics of the function are such that we can't return timeout if the condition is satisfied. This particular wait_timeout (since we are doing it in a loop to protect against spurious wakes) may have timed-out with the condition satisfied so we need to check that first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the difference between a checking after a timeout and not checking semantically observable?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantic difference is whether or not the contract for the function is observed which is that condition == true xor WaitTimeoutResult(true). Since the condition is protected by a mutex, failure to check the condition before exiting would result in a potential race where WaitTimeoutResult(true) is returned even though the condition has been met.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW C++ has this same pattern of checking the result before returning. The main difference is that they don't adjust the wait duration time so you could potentially never wake up if you keep getting spurious wakeups before the timeout.

Copy link
Author

@vlovich vlovich Feb 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the C++ standard (at least as described on cppreference & implemented by libstdc++ & libc++) is broken here because they don't account for time slept in their loop, but there might be a simpler way to rewrite this more like the C++ implementation.

EDIT: Nvm. I totally misread the cppreference. They use absolute timestamps for sleeping so the code is simpler.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think

     let timed_out = Duration::new(0, 0);
     while !condition(&mut *guard) {
         let wait_timer = Instant::now();
         let wait_result = self.wait_timeout(guard, dur)?;
         dur = dur.checked_sub(wait_timer.elapsed()).unwrap_or(timed_out);
         guard = wait_result.0;
         if wait_result.1.timed_out() {
             Ok((guard, WaitTimeoutResult(condition(&mut *guard))))
         }
     }
     Ok((guard, WaitTimeoutResult(true)))

is more readable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about something like

let start = Instant::now();
loop {
    if condition(guard) {
        return Ok((guard, WaitTimeoutResult(true)));
    }
    let timeout = match dur.checked_sub(start.elapsed()) {
        Some(timeout) => timeout,
        None => return Ok((guard, WaitTimeoutResult(false))),
    };
    guard = self.wait_timeout(guard, dur)?.0;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that works.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I think you have the true/false reversed for the WaitTimeoutResult.

@@ -219,6 +219,61 @@ impl Condvar {
}
}

/// Blocks the current thread until this condition variable receives a
/// notification and the required condition is met. There are no spurious
/// wakeups when calling this.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saying that there aren't any spurious wakeups doesn't seem totally accurate - the thread is awake when the closure is executing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I meant to say this function won't return due to spurious wakes. I'll clarify.

/// // Wait for the thread to start up.
/// let &(ref lock, ref cvar) = &*pair;
/// // As long as the value inside the `Mutex` is false, we wait.
/// cvar.wait_until(lock.lock().unwrap(), |ref started| { started });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ref here is unneccessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ignoring the Result returned by wait_until.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Make condition closure accept mut T&.
Clarify spurious wakeup documentation.
Cleanup doc example code.
where F: FnMut(&mut T) -> bool {
let timed_out = Duration::new(0, 0);
loop {
if !condition(&mut *guard) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is backwards, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. It is. I can fix it or rewrite it as above (not sure which is simpler to read). Which would you prefer?

@shepmaster shepmaster added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 3, 2018
Some(timeout) => timeout,
None => return Ok((guard, WaitTimeoutResult(true))),
}
guard = self.wait_timeout(guard, dur)?.0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should wait for timeout rather than dur.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@sfackler sfackler added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Feb 7, 2018
@sfackler
Copy link
Member

sfackler commented Feb 7, 2018

@rust-lang/libs Methods like these originally existed pre-1.0 but were removed to limit the API surface. I think they're valuable, though, particularly the version with the timeout.

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Feb 7, 2018

Team member @sfackler has proposed to merge this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Feb 7, 2018
@alexcrichton
Copy link
Member

These are currently added as stable, but is it intended that they're added as unstable?

@sfackler
Copy link
Member

sfackler commented Feb 7, 2018

Ah yeah they should definitely land unstable.

@alexcrichton
Copy link
Member

Ok! I've checked my box assuming they'll land unstable

@kennytm kennytm added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 11, 2018
@pietroalbini
Copy link
Member

@Kimundi and @withoutboats, there are some checkboxes in #47970 (comment) waiting for you!

@vlovich
Copy link
Author

vlovich commented Feb 13, 2018

I'm unfamiliar with how to tag APIs. How do I make it unstable?

@sfackler
Copy link
Member

You'd replace the stable annotations with #[unstable(feature = "wait_until", issue = "N")] where N is the number of a tracking issue you open on this repo.

Switch feature guards to unstable
Add missing semicolon
Remove mut that's no longer necessary
@dtolnay
Copy link
Member

dtolnay commented Feb 16, 2018

These are being added as unstable, so I think we can go ahead and accept and then consider more closely as a team before stabilization.

@rfcbot fcp cancel
@bors r+

@rfcbot
Copy link

rfcbot commented Feb 16, 2018

@dtolnay proposal cancelled.

@bors
Copy link
Contributor

bors commented Feb 16, 2018

📌 Commit 6fe2d1d has been approved by dtolnay

@rfcbot rfcbot removed the proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. label Feb 16, 2018
@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). labels Feb 16, 2018
Vitali Lovich added 3 commits February 16, 2018 20:00
Also fix some code snippets in documentation.
@sfackler
Copy link
Member

@bors r=dtolnay

@bors
Copy link
Contributor

bors commented Feb 20, 2018

📌 Commit 14b403c has been approved by dtolnay

Manishearth added a commit to Manishearth/rust that referenced this pull request Feb 25, 2018
Add Condvar APIs not susceptible to spurious wake

Provide wait_until and wait_timeout_until helper wrappers that aren't susceptible to spurious wake.
Additionally wait_timeout_until makes it possible to more easily write code that waits for a fixed amount of time in face of spurious wakes since otherwise each user would have to do math on adjusting the duration.

Implements rust-lang#47960.
kennytm added a commit to kennytm/rust that referenced this pull request Feb 25, 2018
Add Condvar APIs not susceptible to spurious wake

Provide wait_until and wait_timeout_until helper wrappers that aren't susceptible to spurious wake.
Additionally wait_timeout_until makes it possible to more easily write code that waits for a fixed amount of time in face of spurious wakes since otherwise each user would have to do math on adjusting the duration.

Implements rust-lang#47960.
bors added a commit that referenced this pull request Feb 25, 2018
Rollup of 17 pull requests

- Successful merges: #47964, #47970, #48076, #48115, #48166, #48281, #48297, #48302, #48362, #48369, #48489, #48491, #48494, #48517, #48529, #48235, #48330
- Failed merges:
@bors bors merged commit 14b403c into rust-lang:master Feb 25, 2018
@vlovich vlovich deleted the condvar_wait_until branch December 2, 2020 01:22
@dtolnay dtolnay self-assigned this Mar 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants