pub struct MockExecutor {
shared: Arc<Shared>,
}
Expand description
Executor for running tests with mocked environment
For test cases which don’t actually wait for anything in the real world.
This is the executor.
It implements Spawn
and ToplevelBlockOn
It will usually be used as part of a MockRuntime
.
To run futures, call ToplevelBlockOn::block_on
§Restricted environment
Tests run with this executor must not attempt to block on anything “outside”: every future that anything awaits must (eventually) be woken directly by some other task in the same test case.
(By directly we mean that the Waker::wake
call is made
by that waking future, before that future itself awaits anything.)
§Panics
The executor will panic
if the toplevel future (passed to block_on
)
doesn’t complete (without externally blocking),
but instead waits for something.
The executor will malfunction or panic if reentered.
(Eg, if block_on
is reentered.)
Fields§
Mutable state
Implementations§
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn with_scheduling(scheduling: SchedulingPolicy) -> Self
pub fn with_scheduling(scheduling: SchedulingPolicy) -> Self
Make a MockExecutor
with a specific SchedulingPolicy
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn spawn_identified(
&self,
desc: impl Display,
fut: impl Future<Output = ()> + Send + 'static,
) -> impl Debug + Clone + Send + 'static
pub fn spawn_identified( &self, desc: impl Display, fut: impl Future<Output = ()> + Send + 'static, ) -> impl Debug + Clone + Send + 'static
Spawn a task and return something to identify it
desc
should Display
as some kind of short string (ideally without spaces)
and will be used in the Debug
impl and trace log messages from MockExecutor
.
The returned value is an opaque task identifier which is very cheap to clone
and which can be used by the caller in debug logging,
if it’s desired to correlate with the debug output from MockExecutor
.
Most callers will want to ignore it.
This method is infallible. (The MockExecutor
cannot be shut down.)
Sourcepub fn spawn_join<T: Debug + Send + 'static>(
&self,
desc: impl Display,
fut: impl Future<Output = T> + Send + 'static,
) -> impl Future<Output = T>
pub fn spawn_join<T: Debug + Send + 'static>( &self, desc: impl Display, fut: impl Future<Output = T> + Send + 'static, ) -> impl Future<Output = T>
Spawn a task and return its output for further usage
desc
should Display
as some kind of short string (ideally without spaces)
and will be used in the Debug
impl and trace log messages from MockExecutor
.
Source§impl MockExecutor
impl MockExecutor
Sourcefn spawn_thread_inner<F, T>(&self, f: F) -> <Self as Blocking>::ThreadHandle<T>
fn spawn_thread_inner<F, T>(&self, f: F) -> <Self as Blocking>::ThreadHandle<T>
Implementation of spawn_blocking
and blocking_io
Source§impl MockExecutor
impl MockExecutor
Sourcefn execute_to_completion(&self, main_fut: Pin<&'_ mut dyn Future<Output = ()>>)
fn execute_to_completion(&self, main_fut: Pin<&'_ mut dyn Future<Output = ()>>)
Keep polling tasks until nothing more can be done
Ie, stop when awake
is empty and progressing_until_stalled
is None
.
Sourcefn execute_until_first_stall(
&self,
main_fut: Pin<&'_ mut dyn Future<Output = ()>>,
)
fn execute_until_first_stall( &self, main_fut: Pin<&'_ mut dyn Future<Output = ()>>, )
Keep polling tasks until awake
is empty
(Ignores progressing_until_stalled
- so if one is active,
will return when all other tasks have blocked.)
§Panics
Might malfunction or panic if called reentrantly
Sourcefn executor_main_loop(&self, main_fut: Pin<&'_ mut dyn Future<Output = ()>>)
fn executor_main_loop(&self, main_fut: Pin<&'_ mut dyn Future<Output = ()>>)
Keep polling tasks until awake
is empty (inner, executor main loop)
This is only called from MockExecutor::execute_until_first_stall
,
so it could also be called execute_until_first_stall_inner
.
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn progress_until_stalled(&self) -> impl Future<Output = ()>
pub fn progress_until_stalled(&self) -> impl Future<Output = ()>
Run tasks in the current executor until every other task is waiting
§Panics
Might malfunction or panic if more than one such call is running at once.
(Ie, you must .await
or drop the returned Future
before calling this method again.)
Must be called and awaited within a future being run by self
.
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn subthread_spawn<T: Send + 'static>(
&self,
desc: impl Display,
call: impl FnOnce() -> T + Send + 'static,
) -> impl Future<Output = Result<T, Box<dyn Any + Send>>> + Unpin + Send + Sync + 'static
pub fn subthread_spawn<T: Send + 'static>( &self, desc: impl Display, call: impl FnOnce() -> T + Send + 'static, ) -> impl Future<Output = Result<T, Box<dyn Any + Send>>> + Unpin + Send + Sync + 'static
Spawn a “Subthread”, for processing in a sync context
call
will be run on a separate thread, called a “Subthread”.
But it will not run simultaneously with the executor, nor with other Subthreads. So Subthreads are somewhat like coroutines.
call
must be capable of making progress without waiting for any other Subthreads.
call
may wait for async futures, using
subthread_block_on_future
.
Subthreads may be used for cpubound activity,
or synchronous IO (such as large volumes of disk activity),
provided that the synchronous code will reliably make progress,
without waiting (directly or indirectly) for any async task or Subthread -
except via subthread_block_on_future
.
§Subthreads vs raw std::thread
threads
Programs using MockExecutor
may use std::thread
threads directly.
However, this is not recommended. There are severe limitations:
- Only a Subthread can re-enter the async context from sync code:
this must be done with
using
subthread_block_on_future
. (Re-entering the executor withblock_on
is not allowed.) - If async tasks want to suspend waiting for synchronous code,
the synchronous code must run on a Subthread.
This allows the
MockExecutor
to know when that synchronous code is still making progress. (This is needed forprogress_until_stalled
and the facilities which use it, such asMockRuntime::advance_until_stalled
.) - Subthreads never run in parallel -
they only run as scheduled deterministically by the
MockExecutor
. So using Subthreads eliminates a source of test nonndeterminism. (Execution order is still varied due to explicitly varying the scheduling policy.)
§Panics, abuse, and malfunctions
If call
panics and unwinds, spawn_subthread
yields Err
.
The application code should to do something about it if this happens,
typically, logging errors, tearing things down, or failing a test case.
If the executor doesn’t run, the subthread will not run either, and will remain stuck. (So, typically, if the thread supposed to run the executor panics, for example because a future or the executor itself panics, all the subthreads will become stuck - effectively, they’ll be leaked.)
spawn_subthread
panics if OS thread spawning fails.
(Like std::thread::spawn()
does.)
MockExecutor
s will malfunction or panic if
any executor invocation method (eg block_on
) is called on a Subthread.
Sourcepub fn subthread_block_on_future<T: Send + 'static>(
&self,
fut: impl Future<Output = T>,
) -> T
pub fn subthread_block_on_future<T: Send + 'static>( &self, fut: impl Future<Output = T>, ) -> T
Call an async Future
from a Subthread
Blocks the Subthread, and arranges to run async tasks,
including fut
, until fut
completes.
fut
is polled on the executor thread, not on the Subthread.
(We may change that in the future, allowing passing a non-Send
future.)
§Panics, abuse, and malfunctions
subthread_block_on_future
will malfunction or panic
if called on a thread that isn’t a Subthread from the same MockExecutor
(ie a thread made with spawn_subthread
).
If fut
itself panics, the executor will panic.
If the executor isn’t running, subthread_block_on_future
will hang indefinitely.
See spawn_subthread
.
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn n_tasks(&self) -> usize
pub fn n_tasks(&self) -> usize
Return the number of tasks running in this executor
One possible use is for a test case to check that task(s) that ought to have exited, have indeed done so.
In the usual case, the answer will be at least 1,
because it counts the future passed to
block_on
(perhaps via MockRuntime::test_with_various
).
Source§impl MockExecutor
impl MockExecutor
Sourcepub fn debug_dump(&self)
pub fn debug_dump(&self)
Dump the executor’s state including backtraces of waiting tasks, to stderr
This is considerably more extensive than simply
MockExecutor as Debug
.
(This is a convenience method, which wraps
MockExecutor::as_debug_dump()
.
§Backtrace salience (possible spurious traces)
Summary
The technique used to capture backtraces when futures sleep is not 100% exact. It will usually show all the actual sleeping sites, but it might also show other backtraces which were part of the implementation of some complex relevant future.
Details
When a future’s implementation wants to sleep,
it needs to record the Waker
(from the Context
)
so that the “other end” can call .wake()
on it later,
when the future should be woken.
Since Context.waker()
gives &Waker
, borrowed from the Context
,
the future must clone the Waker
,
and it must do so in within the poll()
call.
A future which is waiting in a select!
will typically
show multiple traces, one for each branch.
But,
if a future sleeps on one thing, and then when polled again later,
sleeps on something different, without waking up in between,
both backtrace locations will be shown.
And,
a complicated future contraption might clone the Waker
more times.
So not every backtrace will necessarily be informative.
§Panics
Panics on write errors.
Sourcepub fn as_debug_dump(&self) -> DebugDump<'_>
pub fn as_debug_dump(&self) -> DebugDump<'_>
Dump the executor’s state including backtraces of waiting tasks
This is considerably more extensive than simply
MockExecutor as Debug
.
Returns an object for formatting with Debug
.
To simply print the dump to stderr (eg in a test),
use .debug_dump()
.
Backtrace salience (possible spurious traces) -
see .debug_dump()
.
Trait Implementations§
Source§impl Blocking for MockExecutor
impl Blocking for MockExecutor
Source§type ThreadHandle<T: Send + 'static> = Map<Fuse<Receiver<T>>, Box<dyn FnOnce(Result<T, Canceled>) -> T>>
type ThreadHandle<T: Send + 'static> = Map<Fuse<Receiver<T>>, Box<dyn FnOnce(Result<T, Canceled>) -> T>>
spawn_blocking
Source§fn spawn_blocking<F, T>(&self, f: F) -> Self::ThreadHandle<T>
fn spawn_blocking<F, T>(&self, f: F) -> Self::ThreadHandle<T>
Source§fn reenter_block_on<F>(&self, future: F) -> F::Output
fn reenter_block_on<F>(&self, future: F) -> F::Output
Blocking::spawn_blocking
Read moreSource§impl Clone for MockExecutor
impl Clone for MockExecutor
Source§fn clone(&self) -> MockExecutor
fn clone(&self) -> MockExecutor
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read moreSource§impl Debug for MockExecutor
impl Debug for MockExecutor
Source§impl Default for MockExecutor
impl Default for MockExecutor
Source§fn default() -> MockExecutor
fn default() -> MockExecutor
Source§impl From<Data> for MockExecutor
impl From<Data> for MockExecutor
Source§fn from(data: Data) -> MockExecutor
fn from(data: Data) -> MockExecutor
Source§impl Spawn for MockExecutor
impl Spawn for MockExecutor
Source§impl ToplevelBlockOn for MockExecutor
impl ToplevelBlockOn for MockExecutor
impl EnsureSyncSend for MockExecutor
Auto Trait Implementations§
impl Freeze for MockExecutor
impl RefUnwindSafe for MockExecutor
impl Send for MockExecutor
impl Sync for MockExecutor
impl Unpin for MockExecutor
impl UnwindSafe for MockExecutor
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more§impl<Sp> SpawnExt for Sp
impl<Sp> SpawnExt for Sp
§fn spawn<Fut>(&self, future: Fut) -> Result<(), SpawnError>
fn spawn<Fut>(&self, future: Fut) -> Result<(), SpawnError>
()
to
completion. Read more