Name the executors and fail a block_on on the UI thread

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.
This commit is contained in:
2026-09-27 07:08:37 -04:00
parent c50d96e949
commit 7be1efff32
5 changed files with 292 additions and 25 deletions
+4 -18
View File
@@ -491,12 +491,7 @@ fn spawn_login(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, server:
// Only IO and time are enabled; `enable_all()` would also start the
// signal driver, which wants process-wide signal handling that an
// Android app's runtime already owns.
let rt = match tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_io()
.enable_time()
.build()
{
let rt = match crate::net_runtime::build() {
Ok(rt) => rt,
Err(e) => {
let _ = tx.send(LoginMessage::Failed(format!("tokio runtime: {e}")));
@@ -543,12 +538,7 @@ fn spawn_direct_login(
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
// Multi-thread for the reason the browser flow is: a current-thread
// runtime left reqwest's future unpolled on Android.
let rt = match tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_io()
.enable_time()
.build()
{
let rt = match crate::net_runtime::build() {
Ok(rt) => rt,
Err(e) => {
let _ = tx.send(LoginMessage::Failed(format!("tokio runtime: {e}")));
@@ -604,7 +594,7 @@ fn spawn_direct_login(
/// The body of the login flow, split out so the worker above can wrap it in
/// `catch_unwind` without a deeply nested closure.
fn run_login_flow(
rt: tokio::runtime::Runtime,
rt: crate::net_runtime::NetRuntime,
tx: std::sync::mpsc::Sender<LoginMessage>,
server: String,
step: &dyn Fn(&str),
@@ -782,11 +772,7 @@ fn spawn_folder_list(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, pa
// current-thread runtime left reqwest's connection future unpolled on
// Android, so the await never resolved and the thread stopped without
// failing or returning.
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_io()
.enable_time()
.build();
let rt = crate::net_runtime::build();
let Ok(rt) = rt else {
let _ = tx.send(Err("runtime".into()));
return;