-
Notifications
You must be signed in to change notification settings - Fork 37
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
Incorporate selectors into Shuttle #94
Open
FinnitoProductions
wants to merge
15
commits into
awslabs:main
Choose a base branch
from
FinnitoProductions:finn/selectors
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
24eb0b5
Get simple selector working, but with slight interface change (mutabl…
FinnitoProductions a8e1042
Make code compile with stricter linting added back
FinnitoProductions 81b43de
Remove unnecessarily strict mutability constraint
FinnitoProductions 837363e
Add test that unused channels remain functional
FinnitoProductions 3d22d91
Add yield point within selector
FinnitoProductions 37937ba
Simplify internal representation of selector
FinnitoProductions ee50833
Modify interface to align with crossbeam
FinnitoProductions 5e95c31
Consolidate selector logic into single mpsc module
FinnitoProductions d4950f4
Create additional stubs to conform to crossbeam
FinnitoProductions 684ceaa
Prevent handles from being added multiple times to receiving queues
FinnitoProductions 468e385
Revamp channel structure to conform to crossbeam API
FinnitoProductions 9edc37e
Create simple implementation of crossbeam timeouts (constant success …
FinnitoProductions bb1538b
Implement try_recv and try_send in the channel primitive
FinnitoProductions cfda46c
Attempt successful send/receive/select before flipping timeout coin
FinnitoProductions 525a525
Fix double borrow issue in try_send/try_recv
FinnitoProductions File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
//! Selector implementation for multi-producer, single-consumer channels. | ||
|
||
use crate::{sync::mpsc::Receiver, runtime::{execution::ExecutionState, thread}}; | ||
use crossbeam_channel::{TrySelectError, SelectTimeoutError, RecvError}; | ||
use core::fmt::Debug; | ||
use std::time::Duration; | ||
use crate::runtime::task::TaskId; | ||
|
||
/// Represents the return value of a selector; contains an index representing which of the selectables was ready. | ||
#[derive(Debug)] | ||
pub struct SelectedOperation { | ||
/// the index representing which selectable became ready | ||
pub index: usize, | ||
} | ||
|
||
impl SelectedOperation { | ||
pub fn index(&self) -> usize { | ||
self.index | ||
} | ||
|
||
/// Performs a receive on an arbitrary receiver which had been given to the selector that returned this SelectedOperation. | ||
/// TODO: in crossbeam, this method panics if the receiver does not match the one added to the selector -- is this necessary? | ||
pub fn recv<T>(&self, r: &Receiver<T>) -> Result<T, RecvError> { | ||
r.recv().map_err(|_| RecvError) | ||
} | ||
} | ||
|
||
/// Any object which is selectable -- typically used for a receiver. | ||
pub trait Selectable { | ||
/// Attempts to select from the selectable, returning true if anything is present and false otherwise. | ||
fn try_select(&self) -> bool; | ||
/// Adds a queued receiver to the selectable (used when the selector containing the selectable is about to block). | ||
fn add_waiting_receiver(&self, task: TaskId); | ||
/// Removes all instances of a queued receiver from the selectable (used after the selector has been unblocked). | ||
fn delete_waiting_receiver(&self, task: TaskId); | ||
} | ||
|
||
impl<'a> Debug for dyn Selectable + 'a { | ||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
write!(f, "Selectable") | ||
} | ||
} | ||
|
||
fn try_select(handles: &mut [(&dyn Selectable, usize)]) -> Result<SelectedOperation, TrySelectError> { | ||
for handle in handles { | ||
if handle.0.try_select() { | ||
return Ok(SelectedOperation{index: handle.1}) | ||
} | ||
} | ||
Err(TrySelectError{}) | ||
} | ||
|
||
fn select(handles: &mut [(&dyn Selectable, usize)]) -> SelectedOperation { | ||
SelectedOperation { | ||
index: { | ||
if let Ok(SelectedOperation{index: idx}) = try_select(handles) { | ||
idx | ||
} else { | ||
let id = ExecutionState::me(); | ||
|
||
loop { | ||
for handle in &mut *handles { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to add more than once |
||
handle.0.add_waiting_receiver(id); | ||
} | ||
|
||
ExecutionState::with(|state| { | ||
state.get_mut(id).block() | ||
}); | ||
thread::switch(); | ||
|
||
if let Ok(SelectedOperation{index: idx}) = try_select(handles) { | ||
for handle in &mut *handles { | ||
handle.0.delete_waiting_receiver(id); | ||
} | ||
break idx; | ||
} | ||
} | ||
} | ||
}, | ||
} | ||
} | ||
|
||
/// A selector. | ||
#[derive(Debug)] | ||
pub struct Select<'a> { | ||
handles: Vec<(&'a dyn Selectable, usize)>, | ||
} | ||
|
||
impl<'a> Select<'a> { | ||
/// Creates a new instance of the selector with no selectables. | ||
pub fn new() -> Self { | ||
Self { handles: Vec::new() } | ||
} | ||
|
||
/// Adds a new receiving selectable which the selector will wait on. | ||
pub fn recv<T>(&mut self, r: &'a Receiver<T>) -> usize { | ||
self.handles.push((r, self.handles.len())); | ||
self.handles.len() - 1 | ||
} | ||
|
||
/// Attempts to receive from one of the added selectables, returning the index of the given channel if possible. | ||
pub fn try_select(&mut self) -> Result<SelectedOperation, TrySelectError> { | ||
try_select(&mut self.handles) | ||
} | ||
|
||
/// Blocks until a value can be retrieved from one of the given selectables. | ||
pub fn select(&mut self) -> SelectedOperation { | ||
select(&mut self.handles) | ||
} | ||
|
||
/// Blocks until a value can be retrieved from one of the given selectables, returning an error if no value is received | ||
/// before the timeout. | ||
/// TODO: actually enforce timeout | ||
pub fn select_timeout(&mut self, _: Duration) -> Result<SelectedOperation, SelectTimeoutError> { | ||
Ok(self.select()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ mod pct; | |
mod portfolio; | ||
mod replay; | ||
mod rwlock; | ||
mod selector; | ||
mod shrink; | ||
mod thread; | ||
mod timeout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use std::time::Duration; | ||
|
||
use shuttle::sync::mpsc::{Select, channel}; | ||
use shuttle::{check_dfs}; | ||
use test_log::test; | ||
use shuttle::thread; | ||
|
||
#[test] | ||
fn selector_one_channel() { | ||
check_dfs( | ||
move || { | ||
let (s, r) = channel(); | ||
|
||
let mut selector = Select::new(); | ||
selector.recv(&r); | ||
|
||
s.send(5).unwrap(); | ||
|
||
let op = selector.select(); | ||
assert_eq!(op.index, 0); | ||
|
||
let val = r.recv().unwrap(); | ||
assert_eq!(val, 5); | ||
}, | ||
None, | ||
); | ||
} | ||
|
||
#[test] | ||
fn selector_multi_channel() { | ||
check_dfs( | ||
move || { | ||
let (_, r1) = channel::<i32>(); | ||
let (s2, r2) = channel(); | ||
|
||
let mut selector = Select::new(); | ||
selector.recv(&r1); | ||
selector.recv(&r2); | ||
|
||
s2.send(81).unwrap(); | ||
|
||
let op = selector.select(); | ||
assert_eq!(op.index, 1); | ||
|
||
let val = r2.recv().unwrap(); | ||
assert_eq!(val, 81); | ||
}, | ||
None, | ||
); | ||
} | ||
|
||
#[test] | ||
fn try_select_empty_selector() { | ||
check_dfs( | ||
move || { | ||
assert!(Select::new().try_select().is_err()) | ||
}, | ||
None, | ||
); | ||
} | ||
|
||
#[test] | ||
fn select_unused_channel_functional() { | ||
check_dfs( | ||
move || { | ||
let (s1, r1) = channel(); | ||
let (s2, r2) = channel(); | ||
|
||
let mut selector = Select::new(); | ||
selector.recv(&r1); | ||
selector.recv(&r2); | ||
|
||
s2.send(81).unwrap(); | ||
|
||
let op = selector.select(); | ||
assert_eq!(op.index, 1); | ||
|
||
let val = r2.recv().unwrap(); | ||
assert_eq!(val, 81); | ||
|
||
s1.send(198).unwrap(); | ||
let val = r1.recv().unwrap(); | ||
assert_eq!(val, 198); | ||
}, | ||
None, | ||
); | ||
} | ||
|
||
#[test] | ||
fn select_multi_threaded() { | ||
check_dfs( | ||
move || { | ||
let (_, r1) = channel::<i32>(); | ||
let (s2, r2) = channel(); | ||
|
||
let mut selector = Select::new(); | ||
selector.recv(&r1); | ||
selector.recv(&r2); | ||
|
||
thread::spawn(move || { | ||
thread::sleep(Duration::from_millis(15)); | ||
s2.send(5).unwrap(); | ||
}); | ||
|
||
let op = selector.select(); | ||
assert_eq!(op.index, 1); | ||
|
||
let val = r2.recv().unwrap(); | ||
assert_eq!(val, 5); | ||
}, | ||
None, | ||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add more tests: 3 threads |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back 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.
Is this in the public API?