pub struct Conduit<T> { /* private fields */ }Expand description
A conduit for a simplified request-response communication between asynchronous
tasks that allows an owner task to listen for requests for a resource T from
any number of requester tasks, and to then asynchronously serve such requests.
The Conduit should belong to the owner task, as it allows to listen for and
serve the requests. The conduit can spawn any number of linked Retrievers,
which the requester tasks may use to asynchronously retrieve the resource T.
§Error handling
The conduit-retriever pair implements a communication method between asynchronous
tasks, so it is only natural that communication errors may arise. Suppose two
tasks are communicating: one owns a conduit, and the other owns a linked retriever.
Requester task then initiates retrieving of the resource T from the owner task.
This process is not atomic:
- the requester task sends a request,
- the owner task receives the request,
- the owner task sends a response,
- the requester task receives the response.
Anywhere in this process one of the two asynchronous tasks may stop existing.
Furthermore, even if both tasks remain in existence, the owner task may choose
to not listen to incoming requests, or to not serve them (e.g., there is an
error producing the resource T).
This implementation chooses a largely error-less approach to dealing with these uncertainties:
- The requester task may choose to either wait for the response indefinitely or to deal with lack of response. Another option is to wait for a response within a timeout.
- The owner task may choose whether to listen to requests or whether to send responses.
Any breakage of communication caused by either of the tasks exiting is consciously treated as a normal part of the application lifecycle: it does not cause panics and is not logged.
§Example
use strut_sync::Conduit;
use strut_sync::Retriever;
use tokio::sync::oneshot;
#[tokio::main]
async fn main() {
// Create a conduit and a related retriever
let conduit: Conduit<String> = Conduit::new();
let retriever: Retriever<String> = conduit.retriever();
// Spawn owner task
let owner = tokio::spawn(async move {
// Stand-in for a valuable resource
let resource: String = "valuable_resource".to_string();
// Await a request
let sender: oneshot::Sender<String> = conduit.requested().await;
// Send a response
sender.send(resource).unwrap();
});
// Spawn requester task
let requester = tokio::spawn(async move {
// Acquire the resource
let acquired_resource: String = retriever.request().await.unwrap();
// Assert it is what is expected
assert_eq!(&acquired_resource, "valuable_resource");
});
requester.await.unwrap();
owner.await.unwrap();
}Implementations§
Source§impl<T> Conduit<T>
impl<T> Conduit<T>
Sourcepub async fn requested(&self) -> Sender<T>
pub async fn requested(&self) -> Sender<T>
Waits until the resource T is requested from any of the connected
Retrievers. Upon first such request, returns the one-off sender through
which the resource T should be sent back to whoever requested it.
This method should be repeatedly awaited on by the asynchronous task that
owns the resource T. Only one asynchronous task may listen for and
serve requests at any given moment, which is the limitation that comes
from the nature of the mpsc channels (see mpsc::Receiver::recv).
§Return type
It is notable that this method returns a oneshot::Sender, not an Option
of it.
Technically, it is possible to receive None from mpsc::Receiver::recv.
This happens iff there are no buffered messages in the mpsc channel and
the mpsc channel is closed. The mpsc channel is closed when either all
senders are dropped, or when mpsc::Receiver::close is called.
This conduit owns at least one copy of mpsc::Sender (requester_template),
so dropping all senders without also dropping this conduit is not possible.
Then, this conduit does not call close and also
does not expose ways to call it externally.
Thus, this method takes a calculated risk of unwrapping the Option before
returning.