Expand description
The task module.
The task module contains the code that manages spawned tasks and provides a
safe API for the rest of the runtime to use. Each task in a runtime is
stored in an OwnedTasks
or LocalOwnedTasks
object.
§Task reference types
A task is usually referenced by multiple handles, and there are several types of handles.
-
OwnedTask
- tasks stored in anOwnedTasks
orLocalOwnedTasks
are of this reference type. -
JoinHandle
- each task has aJoinHandle
that allows access to the output of the task. -
Waker
- every waker for a task has this reference type. There can be any number of waker references. -
Notified
- tracks whether the task is notified. -
Unowned
- this task reference type is used for tasks not stored in any runtime. Mainly used for blocking tasks, but also in tests.
The task uses a reference count to keep track of how many active references
exist. The Unowned
reference type takes up two ref-counts. All other
reference types take up a single ref-count.
Besides the waker type, each task has at most one of each reference type.
§State
The task stores its state in an atomic usize
with various bitfields for the
necessary information. The state has the following bitfields:
-
RUNNING
- Tracks whether the task is currently being polled or cancelled. This bit functions as a lock around the task. -
COMPLETE
- Is one once the future has fully completed and has been dropped. Never unset once set. Never set together with RUNNING. -
NOTIFIED
- Tracks whether a Notified object currently exists. -
CANCELLED
- Is set to one for tasks that should be cancelled as soon as possible. May take any value for completed tasks. -
JOIN_INTEREST
- Is set to one if there exists aJoinHandle
. -
JOIN_WAKER
- Acts as an access control bit for the join handle waker. The protocol for its usage is described below.
The rest of the bits are used for the ref-count.
§Fields in the task
The task has various fields. This section describes how and when it is safe to access a field.
-
The state field is accessed with atomic instructions.
-
The
OwnedTask
reference has exclusive access to theowned
field. -
The Notified reference has exclusive access to the
queue_next
field. -
The
owner_id
field can be set as part of construction of the task, but is otherwise immutable and anyone can access the field immutably without synchronization. -
If COMPLETE is one, then the
JoinHandle
has exclusive access to the stage field. If COMPLETE is zero, then the RUNNING bitfield functions as a lock for the stage field, and it can be accessed only by the thread that set RUNNING to one. -
The waker field may be concurrently accessed by different threads: in one thread the runtime may complete a task and read the waker field to invoke the waker, and in another thread the task’s
JoinHandle
may be polled, and if the task hasn’t yet completed, theJoinHandle
may write a waker to the waker field. TheJOIN_WAKER
bit ensures safe access by multiple threads to the waker field using the following rules:-
JOIN_WAKER
is initialized to zero. -
If
JOIN_WAKER
is zero, then theJoinHandle
has exclusive (mutable) access to the waker field. -
If
JOIN_WAKER
is one, then theJoinHandle
has shared (read-only) access to the waker field. -
If
JOIN_WAKER
is one and COMPLETE is one, then the runtime has shared (read-only) access to the waker field. -
If the
JoinHandle
needs to write to the waker field, then theJoinHandle
needs to (i) successfully setJOIN_WAKER
to zero if it is not already zero to gain exclusive access to the waker field per rule 2, (ii) write a waker, and (iii) successfully setJOIN_WAKER
to one. -
The
JoinHandle
can changeJOIN_WAKER
only if COMPLETE is zero (i.e. the task hasn’t yet completed).
Rule 6 implies that the steps (i) or (iii) of rule 5 may fail due to a race. If step (i) fails, then the attempt to write a waker is aborted. If step (iii) fails because COMPLETE is set to one by another thread after step (i), then the waker field is cleared. Once COMPLETE is one (i.e. task has completed), the
JoinHandle
will not modifyJOIN_WAKER
. After the runtime sets COMPLETE to one, it invokes the waker if there is one. -
All other fields are immutable and can be accessed immutably without synchronization by anyone.
§Safety
This section goes through various situations and explains why the API is safe in that situation.
§Polling or dropping the future
Any mutable access to the future happens after obtaining a lock by modifying the RUNNING field, so exclusive access is ensured.
When the task completes, exclusive access to the output is transferred to
the JoinHandle
. If the JoinHandle
is already dropped when the transition to
complete happens, the thread performing that transition retains exclusive
access to the output and should immediately drop it.
§Non-Send futures
If a future is not Send, then it is bound to a LocalOwnedTasks
. The future
will only ever be polled or dropped given a LocalNotified
or inside a call
to LocalOwnedTasks::shutdown_all
. In either case, it is guaranteed that the
future is on the right thread.
If the task is never removed from the LocalOwnedTasks
, then it is leaked, so
there is no risk that the task is dropped on some other thread when the last
ref-count drops.
§Non-Send output
When a task completes, the output is placed in the stage of the task. Then,
a transition that sets COMPLETE to true is performed, and the value of
JOIN_INTEREST
when this transition happens is read.
If JOIN_INTEREST
is zero when the transition to COMPLETE happens, then the
output is immediately dropped.
If JOIN_INTEREST
is one when the transition to COMPLETE happens, then the
JoinHandle
is responsible for cleaning up the output. If the output is not
Send, then this happens:
- The output is created on the thread that the future was polled on. Since only non-Send futures can have non-Send output, the future was polled on the thread that the future was spawned from.
- Since
JoinHandle<Output>
is not Send if Output is not Send, theJoinHandle
is also on the thread that the future was spawned from. - Thus, the
JoinHandle
will not move the output across threads when it takes or drops the output.
§Recursive poll/shutdown
Calling poll from inside a shutdown call or vice-versa is not prevented by
the API exposed by the task module, so this has to be safe. In either case,
the lock in the RUNNING bitfield makes the inner call return immediately. If
the inner call is a shutdown
call, then the CANCELLED bit is set, and the
poll call will notice it when the poll finishes, and the task is cancelled
at that point.
Re-exports§
pub use self::error::JoinError;
pub use id::id;
pub use id::try_id;
pub use id::Id;
pub use self::abort::AbortHandle;
pub use self::join::JoinHandle;
Modules§
- abort 🔒
- core 🔒Core task module.
- error 🔒
- harness 🔒
- id 🔒
- join 🔒
- list 🔒This module has containers for storing the tasks spawned on a scheduler. The
OwnedTasks
container is thread-safe but can only store tasks that implement Send. TheLocalOwnedTasks
container is not thread safe, but can store non-Send tasks. - raw 🔒
- state 🔒
- waker 🔒
Structs§
- A non-Send variant of Notified with the invariant that it is on a thread where it is safe to poll it.
- Notified 🔒A task was notified.
- Task 🔒An owned handle to the task, tracked by ref count.
- Hooks for scheduling tasks which are needed in the task harness.
- A task that is not owned by any
OwnedTasks
. Used for blocking tasks. This type holds two ref-counts.
Traits§
- Schedule 🔒
Functions§
- new_task 🔒This is the constructor for a new task. Three references to the task are created. The first task reference is usually put into an
OwnedTasks
immediately. The Notified is sent to the scheduler as an ordinary notification. - unowned 🔒Creates a new task with an associated join handle. This method is used only when the task is not going to be stored in an
OwnedTasks
list.
Type Aliases§
- Result 🔒Task result sent back.