Imported from Capsem triage report ui-04-tui-runtime-and-client-per-poll.md.
- Severity:
low
- Category:
performance
- Area:
capsem-tui
- Location:
crates/capsem-tui/src/gateway_provider.rs:102-133 and :66-72
- Confidence:
verified
Summary
GatewayProvider::load() (the polling path) and invoke() (the action path)
each construct a brand-new single-threaded tokio runtime per call. Polling runs
on the RefreshBridge worker at the configured refresh_ms (default 1000ms,
floored at 100ms — main.rs:50,128-132). On top of that, every
GatewayProvider holds a reqwest::Client created with
reqwest::Client::new() (line 69); building a reqwest::Client allocates a new
connection pool and TLS configuration. So each refresh spins up and tears down a
full async runtime, and the same provider instance is cloned into both the
refresh thread and the control thread.
Evidence
// gateway_provider.rs:126 (StateProvider::load, called on every refresh)
fn load(&self) -> Result<AppState> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build() // new runtime every poll
.context("build capsem-tui gateway provider runtime")?;
runtime.block_on(self.load_async())
}
// gateway_provider.rs:102 (invoke, same pattern per action)
pub fn invoke(&self, action: &ControlAction) -> Result<ActionOutcome> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all().build()...;
runtime.block_on(self.invoke_async(action))
}
RefreshBridge::spawn_with_loader calls loader() (== provider.load()) once
per request() (main.rs:398-415), and run_loop issues a request() every
refresh_interval (main.rs:238-243).
Impact
Wasted CPU and allocations every second for the lifetime of the TUI: a full
tokio runtime build/teardown plus the per-provider connection pool not being
reused across polls. Not a correctness bug and not a tight loop (the
in_flight guard prevents overlap), but unnecessary steady-state churn in a
long-lived UI process.
Suggested fix
Build one runtime (or one Handle) when the RefreshBridge/ControlBridge
worker thread starts and reuse it across load/invoke calls. The
reqwest::Client is already retained on the provider, so the main remaining win
is hoisting the runtime out of the per-call path.
Triage
Confirmed from the local reviewed report in /Users/elie/git/capsem/tmp/bugs/ui-04-tui-runtime-and-client-per-poll.md. Track implementation in the triage sprint; add regression coverage before fixing.
Imported from Capsem triage report
ui-04-tui-runtime-and-client-per-poll.md.lowperformancecapsem-tuicrates/capsem-tui/src/gateway_provider.rs:102-133and:66-72verifiedSummary
GatewayProvider::load()(the polling path) andinvoke()(the action path)each construct a brand-new single-threaded tokio runtime per call. Polling runs
on the
RefreshBridgeworker at the configuredrefresh_ms(default 1000ms,floored at 100ms —
main.rs:50,128-132). On top of that, everyGatewayProviderholds areqwest::Clientcreated withreqwest::Client::new()(line 69); building areqwest::Clientallocates a newconnection pool and TLS configuration. So each refresh spins up and tears down a
full async runtime, and the same provider instance is cloned into both the
refresh thread and the control thread.
Evidence
RefreshBridge::spawn_with_loadercallsloader()(==provider.load()) onceper
request()(main.rs:398-415), andrun_loopissues arequest()everyrefresh_interval(main.rs:238-243).Impact
Wasted CPU and allocations every second for the lifetime of the TUI: a full
tokio runtime build/teardown plus the per-provider connection pool not being
reused across polls. Not a correctness bug and not a tight loop (the
in_flightguard prevents overlap), but unnecessary steady-state churn in along-lived UI process.
Suggested fix
Build one runtime (or one
Handle) when theRefreshBridge/ControlBridgeworker thread starts and reuse it across
load/invokecalls. Thereqwest::Clientis already retained on the provider, so the main remaining winis hoisting the runtime out of the per-call path.
Triage
Confirmed from the local reviewed report in
/Users/elie/git/capsem/tmp/bugs/ui-04-tui-runtime-and-client-per-poll.md. Track implementation in the triage sprint; add regression coverage before fixing.