architecture.md §7.1 stated five executors and their thread counts, and no code named them. dr_ui::executors now does: the Executor enum with each one's thread name and the count §7.1 gives with its reason, and spawn, which starts a thread named <executor>:<role> and marks it with the executor it belongs to. The counts are the stated budget, not yet a bound: a job still gets a thread of its own when it starts. run marks its own thread as the UI executor before it builds the window. net_runtime::build now returns a NetRuntime whose block_on asserts, in debug and test builds, that the caller is not that thread; everything else derefs to the tokio runtime. The login, folder-list and remote-folder workers built the same runtime by hand and now take it from net_runtime, so their block_on is guarded too. Tests: a block_on on a thread marked as the UI executor panics naming the UI thread; the same call on a worker returns; a spawned thread carries its name and executor.
64 lines
2.5 KiB
Rust
64 lines
2.5 KiB
Rust
//! The tokio runtime every network worker is built from.
|
|
//!
|
|
//! One function rather than a builder repeated at each call site, because the
|
|
//! choice it encodes is not obvious and was wrong in eleven places at once.
|
|
//!
|
|
//! **Why not `new_current_thread`.** A current-thread runtime drives its
|
|
//! reactor only while the thread is inside `block_on`. On Android that left
|
|
//! reqwest's connection future unpolled: the await never resolved, so the
|
|
//! worker neither failed nor returned. Every symptom was an absence — no error,
|
|
//! no panic for `catch_unwind` to catch, no log line, and a channel that closed
|
|
//! only when the thread was finally torn down. What the user saw was a sign-in
|
|
//! that stopped, and a library that quietly scanned itself rather than adopting
|
|
//! the shards the server already held.
|
|
//!
|
|
//! A multi-thread runtime owns worker threads that poll the reactor regardless.
|
|
//! One worker is enough: these are single request-response flows, not
|
|
//! throughput-bound work.
|
|
//!
|
|
//! **Why not `enable_all`.** That also starts the signal driver, which wants
|
|
//! process-wide signal handling. Inside an Android app the runtime already owns
|
|
//! that, and a worker thread claiming it is asking for trouble. These flows need
|
|
//! IO (reqwest) and time (poll intervals, timeouts) and nothing else.
|
|
|
|
//!
|
|
//! **Why a wrapper.** [`NetRuntime::block_on`] is tokio's, behind the guard
|
|
//! that fails a debug or test build when it is called on the UI thread
|
|
//! (NFR-ARCH-1; see `executors::assert_not_ui`). Everything else derefs to the
|
|
//! runtime.
|
|
|
|
/// Build a runtime suitable for a network worker thread.
|
|
///
|
|
/// Call from the spawned thread, not from the caller: the runtime must live on
|
|
/// the thread that blocks on it — a thread from `executors::spawn`, normally
|
|
/// on `Executor::Network`.
|
|
pub fn build() -> std::io::Result<NetRuntime> {
|
|
tokio::runtime::Builder::new_multi_thread()
|
|
.worker_threads(1)
|
|
.enable_io()
|
|
.enable_time()
|
|
.build()
|
|
.map(NetRuntime)
|
|
}
|
|
|
|
/// A network worker's runtime, whose `block_on` refuses the UI thread.
|
|
pub struct NetRuntime(tokio::runtime::Runtime);
|
|
|
|
impl NetRuntime {
|
|
/// TRACES: NFR-ARCH-1 | NFR-P9
|
|
/// Tokio's `block_on`, after asserting the caller is not the UI thread.
|
|
#[track_caller]
|
|
pub fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
|
|
crate::executors::assert_not_ui("block_on");
|
|
self.0.block_on(future)
|
|
}
|
|
}
|
|
|
|
impl std::ops::Deref for NetRuntime {
|
|
type Target = tokio::runtime::Runtime;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|