Skip to content

refactor(device): take a wasm-portable timer instead of tokio::time - #840

Open
4ni1ak wants to merge 1 commit into
AprilNEA:masterfrom
4ni1ak:issue/826-portable-timer
Open

refactor(device): take a wasm-portable timer instead of tokio::time#840
4ni1ak wants to merge 1 commit into
AprilNEA:masterfrom
4ni1ak:issue/826-portable-timer

Conversation

@4ni1ak

@4ni1ak 4ni1ak commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

openlogi-device timed its inventory probes, writes and pairing session with
tokio::time, which compiles for wasm32-unknown-unknown and panics the first
time a timer is polled. The wasm (portable crates) job is a cargo check, so
the crate sat on the portable list carrying a timer that cannot run there.

Fixes #826

The choice

The issue frames it as futures-timer — which costs a global timer thread on
native — against a cfg-split shim. The tie-breaker is that futures-timer is
already in the graph: openlogi-hidpp, the other crate on the portable list,
times its own reads with it, so every native build that links openlogi-device
pays for that thread whether or not this crate joins. Taking it costs nothing
new and removes the cfg entirely.

So the split lives in the manifest, not the code. src/time.rs exposes sleep
and timeout; no call site learns which target it is on, which is the
constraint the issue sets.

Changes

openlogi-device/src/time.rs (new)

  • sleep wraps futures_timer::Delay.
  • timeout is written once on top of it, because futures-timer ships no
    timeout combinator — that is the reason this module exists rather than each
    call site reaching for Delay. The future is polled ahead of the timer, so
    one that is already ready still wins a zero-length budget, matching what
    tokio::time::timeout documents and what the probe paths rely on when a
    cached answer resolves without suspending.
  • Elapsed is a unit error: every caller in this crate already treated a
    timeout as one opaque outcome (Err(_), .is_err(), .ok()).

Call sites — eleven, across inventory, inventory::probe,
session::{gesture, host_switch, keyboard}, write::{lighting, litra, smartshift} and pairing, plus one test harness guard. grep tokio::time
over the crate now returns only two doc references in the new module.
tokio::sync is untouched; it works on wasm.

Cargo.toml

  • futures-timer = "3.0.4", matching the version openlogi-hidpp pins.
  • The wasm-bindgen feature enabled for the wasm target only. Without it
    futures-timer hands a wasm build its native, thread-backed path — the same
    runtime failure this change is removing, in a different crate. Scoping it to
    the target keeps gloo-timers and send_wrapper out of native builds, and
    since Cargo unifies features it also gives openlogi-hidpp's timers the
    browser path on wasm.

Testing

Linux, x86_64, Rust 1.98.0:

cargo xtask ci wasm          # the job this is about — passes
cargo test -p openlogi-device
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings   # RUSTFLAGS=-D warnings
cargo test --workspace
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items \
  --exclude openlogi-ui --exclude openlogi-desktop --exclude openlogi-overlay --exclude openlogi-agent

All green; 173 tests in openlogi-device.

The feature scoping was checked directly rather than assumed:

$ cargo tree -p openlogi-device --target wasm32-unknown-unknown | grep -cE 'gloo-timers|send_wrapper'
2
$ cargo tree -p openlogi-device | grep -cE 'gloo-timers|send_wrapper'
0

src/time.rs carries three tests: budget met, already-ready future against a
zero budget, and a pending future elapsing.

What this does not prove. The panic it removes is a runtime one on wasm,
and there is no wasm runtime here — cargo check is as far as CI or this host
goes, which is the gap the issue itself names. What is verified is that the
crate no longer references tokio::time, that the browser timer is the one
selected for wasm builds, and that native behaviour is unchanged by the test
suite. Real confirmation belongs with whatever first drives this crate from a
browser (#825).

Not run on this host: tests (macos), cargo-deny, macOS clippy.
No hardware verification applies — no device code changed, only the timers
around it.

`openlogi-device` timed its inventory probes, writes and pairing session
with `tokio::time`. That compiles for `wasm32-unknown-unknown` and panics
the first time a timer is polled, and the `wasm (portable crates)` job is a
`cargo check`, so nothing caught it — the crate was on the portable list
while carrying a timer that cannot run there.

`futures-timer` is the timer both targets share, and it was already in the
graph: `openlogi-hidpp`, the other crate on that list, times its own reads
with it. So the global timer thread it costs on native is paid whether or
not this crate joins, which is what settles the trade-off the issue names.

The cfg lives in the manifest, not in the code: `src/time.rs` exposes
`sleep` and `timeout` and no call site learns which target it is on.
`timeout` is written once on top of `sleep` because futures-timer ships no
such combinator — polling the future ahead of the timer, so an already-ready
future still wins a zero-length budget the way the probe paths assume.

`futures-timer` only reaches for the browser's timer under its
`wasm-bindgen` feature; without it a wasm build silently gets the
thread-backed native path, which is the same runtime failure in a different
crate. The feature is enabled for the wasm target only, so native builds
pull neither `gloo-timers` nor `send_wrapper` — and since Cargo unifies
features, `openlogi-hidpp`'s timers get the browser path on wasm too.

Fixes AprilNEA#826
@4ni1ak
4ni1ak requested a review from AprilNEA as a code owner August 23, 2026 07:57
@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces tokio::time usage in openlogi-device with a portable timer abstraction backed by futures-timer.

  • Adds shared sleep and left-biased timeout helpers.
  • Migrates inventory, pairing, session, and write timing call sites.
  • Enables the browser timer backend specifically for wasm targets and updates the lockfile.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete regressions identified in timer semantics, target feature selection, or migrated call sites.

The portable timeout preserves operation-first tie handling and drop-based cancellation, while Cargo’s target-specific feature resolution selects the browser timer backend for the configured wasm build without affecting native builds.

Important Files Changed

Filename Overview
crates/openlogi-device/src/time.rs Introduces portable sleep and timeout helpers whose tie-breaking and cancellation behavior match the affected call sites.
crates/openlogi-device/Cargo.toml Adds futures-timer and enables its browser backend only for wasm32 targets under Cargo resolver v3.
crates/openlogi-device/src/inventory.rs Migrates inventory probe timeout and retry delay usage to the shared timer abstraction.
crates/openlogi-device/src/inventory/probe.rs Migrates probe and arrival-drain timeout operations while preserving opaque timeout handling.
crates/openlogi-device/src/pairing.rs Replaces the pinned Tokio pairing deadline with the portable delay future.
crates/openlogi-device/src/session/gesture.rs Uses the portable delay for periodic liveness checks inside the existing select loop.
crates/openlogi-device/src/session/host_switch.rs Moves channel-open and HID++ operation budgets to the shared timeout helper.
crates/openlogi-device/src/write/litra.rs Migrates raw report write timing while retaining the existing timeout error mapping.
Cargo.lock Records the browser timer dependencies introduced by the wasm-enabled futures-timer feature.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Callers[Inventory, pairing, sessions, and writes] --> Time[openlogi-device time abstraction]
  Time --> Sleep[futures_timer Delay]
  Time --> Timeout[left-biased timeout combinator]
  Timeout --> Sleep
  Sleep --> Native[Native timer backend]
  Sleep --> Wasm[Browser timer backend on wasm32]
Loading

Reviews (1): Last reviewed commit: "refactor(device): take a wasm-portable t..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openlogi-device: replace tokio::time with a wasm-portable timer

1 participant