google / google/capsem

[triage:ui-04-tui-runtime-and-client-per-poll] TUI builds a fresh tokio runtime + reqwest client on every poll/action

Open
#154 0 comments 0 reactions 0 assignees View on GitHub
type:performance type:UI
Dominant language
Rust
Stars
72
Forks
13
Avg merge
1d 2h
Merged PRs (30d)
5

Description

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 {
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 {
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.

Contributor guide

Open the contributing guide

Research direction

Start with crates/capsem-tui/src/gateway_provider.rs:66-72 and :102-133, then trace RefreshBridge and ControlBridge worker startup in main.rs:238-243 and :398-415. Verify how load and invoke are called, and inspect existing TUI tests before changing the runtime lifecycle. Done means polling and actions reuse worker-owned async resources without per-call runtime construction, with regression coverage added.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.