-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Make test-util paused time fully deterministic #3492
Changes from all commits
4aac8df
b64a572
215ce30
9524503
6cb567f
8fec76b
86b0736
84dd3c5
413159c
6a372b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,13 +25,15 @@ impl RuntimeFlavor { | |
struct FinalConfig { | ||
flavor: RuntimeFlavor, | ||
worker_threads: Option<usize>, | ||
start_paused: Option<bool>, | ||
} | ||
|
||
struct Configuration { | ||
rt_multi_thread_available: bool, | ||
default_flavor: RuntimeFlavor, | ||
flavor: Option<RuntimeFlavor>, | ||
worker_threads: Option<(usize, Span)>, | ||
start_paused: Option<(bool, Span)>, | ||
} | ||
|
||
impl Configuration { | ||
|
@@ -44,6 +46,7 @@ impl Configuration { | |
}, | ||
flavor: None, | ||
worker_threads: None, | ||
start_paused: None, | ||
} | ||
} | ||
|
||
|
@@ -79,31 +82,57 @@ impl Configuration { | |
Ok(()) | ||
} | ||
|
||
fn set_start_paused(&mut self, start_paused: syn::Lit, span: Span) -> Result<(), syn::Error> { | ||
if self.start_paused.is_some() { | ||
return Err(syn::Error::new(span, "`start_paused` set multiple times.")); | ||
} | ||
|
||
let start_paused = parse_bool(start_paused, span, "start_paused")?; | ||
self.start_paused = Some((start_paused, span)); | ||
Ok(()) | ||
} | ||
|
||
fn build(&self) -> Result<FinalConfig, syn::Error> { | ||
let flavor = self.flavor.unwrap_or(self.default_flavor); | ||
use RuntimeFlavor::*; | ||
match (flavor, self.worker_threads) { | ||
(CurrentThread, Some((_, worker_threads_span))) => Err(syn::Error::new( | ||
worker_threads_span, | ||
"The `worker_threads` option requires the `multi_thread` runtime flavor.", | ||
)), | ||
(CurrentThread, None) => Ok(FinalConfig { | ||
flavor, | ||
worker_threads: None, | ||
}), | ||
(Threaded, worker_threads) if self.rt_multi_thread_available => Ok(FinalConfig { | ||
flavor, | ||
worker_threads: worker_threads.map(|(val, _span)| val), | ||
}), | ||
|
||
let worker_threads = match (flavor, self.worker_threads) { | ||
(CurrentThread, Some((_, worker_threads_span))) => { | ||
return Err(syn::Error::new( | ||
worker_threads_span, | ||
"The `worker_threads` option requires the `multi_thread` runtime flavor.", | ||
)) | ||
} | ||
(CurrentThread, None) => None, | ||
(Threaded, worker_threads) if self.rt_multi_thread_available => { | ||
worker_threads.map(|(val, _span)| val) | ||
} | ||
(Threaded, _) => { | ||
let msg = if self.flavor.is_none() { | ||
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled." | ||
} else { | ||
"The runtime flavor `multi_thread` requires the `rt-multi-thread` feature." | ||
}; | ||
Err(syn::Error::new(Span::call_site(), msg)) | ||
return Err(syn::Error::new(Span::call_site(), msg)); | ||
} | ||
} | ||
}; | ||
|
||
let start_paused = match (flavor, self.start_paused) { | ||
(Threaded, Some((_, start_paused_span))) => { | ||
return Err(syn::Error::new( | ||
start_paused_span, | ||
"The `start_paused` option requires the `current_thread` runtime flavor.", | ||
)); | ||
} | ||
(CurrentThread, Some((start_paused, _))) => Some(start_paused), | ||
(_, None) => None, | ||
}; | ||
|
||
Ok(FinalConfig { | ||
flavor, | ||
worker_threads, | ||
start_paused, | ||
}) | ||
} | ||
} | ||
|
||
|
@@ -134,6 +163,16 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::E | |
} | ||
} | ||
|
||
fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> { | ||
match bool { | ||
syn::Lit::Bool(b) => Ok(b.value), | ||
_ => Err(syn::Error::new( | ||
span, | ||
format!("Failed to parse {} as bool.", field), | ||
)), | ||
} | ||
} | ||
|
||
fn parse_knobs( | ||
mut input: syn::ItemFn, | ||
args: syn::AttributeArgs, | ||
|
@@ -174,6 +213,9 @@ fn parse_knobs( | |
"flavor" => { | ||
config.set_flavor(namevalue.lit.clone(), namevalue.span())?; | ||
} | ||
"start_paused" => { | ||
config.set_start_paused(namevalue.lit.clone(), namevalue.span())?; | ||
} | ||
"core_threads" => { | ||
let msg = "Attribute `core_threads` is renamed to `worker_threads`"; | ||
return Err(syn::Error::new_spanned(namevalue, msg)); | ||
|
@@ -204,11 +246,11 @@ fn parse_knobs( | |
macro_name | ||
) | ||
} | ||
"flavor" | "worker_threads" => { | ||
"flavor" | "worker_threads" | "start_paused" => { | ||
format!("The `{}` attribute requires an argument.", name) | ||
} | ||
name => { | ||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`", name) | ||
format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`", name) | ||
} | ||
}; | ||
return Err(syn::Error::new_spanned(path, msg)); | ||
|
@@ -235,6 +277,9 @@ fn parse_knobs( | |
if let Some(v) = config.worker_threads { | ||
rt = quote! { #rt.worker_threads(#v) }; | ||
} | ||
if let Some(v) = config.start_paused { | ||
rt = quote! { #rt.start_paused(#v) }; | ||
} | ||
Comment on lines
+280
to
+282
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. Since making the test deterministic requires calling it on the builder, I agree that it should be added to the macro, but we should document it on both 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. Done
Comment on lines
+280
to
+282
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. Currently this will fail to compile if used without the 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. Yeah - it looks like there's some detection for the multi-threaded runtime but it involves 2 macros and cfg'd imports in tokio which I don't think can easily scale to multiple features. 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. I don't personally feel super strongly about supporting this in the macros, fwiw. I'd be fine leaving it out and adjusting the test to manually make the runtime. 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. Can we not just do e.g.:
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. I have a memory with there being some sort of issue with forwarding the features to the tokio-macros crate (recall, it is a separate crate), but I'm not sure what it was. 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. 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. Sure, I'll give it a try tonight. If it works, we could remove the 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. Let us just keep the others for now. 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. Ah - the problem with that approach is that Cargo has no way currently to say "enable this feature on an optional dependency, but don't activate that dependency". So if we try to propagate features into tokio-macros, turning on any of those features in tokio automatically pulls in tokio-macros. |
||
|
||
let header = { | ||
if is_test { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,9 @@ pub struct Builder { | |
/// Whether or not to enable the time driver | ||
enable_time: bool, | ||
|
||
/// Whether or not the clock should start paused. | ||
start_paused: bool, | ||
|
||
/// The number of worker threads, used by Runtime. | ||
/// | ||
/// Only used when not using the current-thread executor. | ||
|
@@ -110,6 +113,9 @@ impl Builder { | |
// Time defaults to "off" | ||
enable_time: false, | ||
|
||
// The clock starts not-paused | ||
start_paused: false, | ||
|
||
// Default to lazy auto-detection (one thread per CPU core) | ||
worker_threads: None, | ||
|
||
|
@@ -386,6 +392,7 @@ impl Builder { | |
}, | ||
enable_io: self.enable_io, | ||
enable_time: self.enable_time, | ||
start_paused: self.start_paused, | ||
} | ||
} | ||
|
||
|
@@ -489,6 +496,31 @@ cfg_time! { | |
} | ||
} | ||
|
||
cfg_test_util! { | ||
impl Builder { | ||
/// Controls if the runtime's clock starts paused or advancing. | ||
/// | ||
/// Pausing time requires the current-thread runtime; construction of | ||
/// the runtime will panic otherwise. | ||
Comment on lines
+503
to
+504
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. Where is this panic? 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. The call to |
||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use tokio::runtime; | ||
/// | ||
/// let rt = runtime::Builder::new_current_thread() | ||
/// .enable_time() | ||
/// .start_paused(true) | ||
/// .build() | ||
/// .unwrap(); | ||
/// ``` | ||
pub fn start_paused(&mut self, start_paused: bool) -> &mut Self { | ||
self.start_paused = start_paused; | ||
self | ||
} | ||
} | ||
} | ||
|
||
cfg_rt_multi_thread! { | ||
impl Builder { | ||
fn build_threaded_runtime(&mut self) -> io::Result<Runtime> { | ||
|
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.
The new attribute should be mentioned in the catch-all a few lines further down.
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.
Done