Skip to content

[Bug] Desktop fails to start: 'overflow when subtracting duration from instant' panic in embedded Rust backend shortly after boot #554

Description

@whobat

Summary

routa-desktop.exe crashes during startup with a Rust panic:

thread 'main' panicked at /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library\std\src\time.rs:445:33:
overflow when subtracting duration from instant

The embedded Rust backend reaches the point of binding to 127.0.0.1:3210, then panics before the WebView2 window becomes visible. The trailing Failed to unregister class Chrome_WidgetWin_0. Error = 1412 is post-crash teardown, not the cause.

The crash reproduces deterministically when the machine has been up for less than ~20 minutes, which strongly suggests an Instant::now() - Duration (or equivalent monotonic-clock subtraction) somewhere in startup code where the subtracted Duration exceeds system uptime. On Windows, std::time::Instant is monotonic from boot, so any subtraction that assumes a non-zero baseline will underflow on a freshly-booted system.

Environment

  • App version: routa-desktop 0.1.0 (strings dumped from the bundled binary)
  • Tauri: 2.10.3
  • Wry: 0.54.4
  • WebView2 Runtime: 148.0.3967.70
  • OS: Windows 11 Pro 26200 (x64)
  • Install path: C:\Routa Desktop\
  • System uptime at crash: 1181 s (~19 min 41 s) — this matters; see below

Reproduction

  1. Reboot Windows (or have a freshly-booted system with low uptime).
  2. Within ~20 minutes of boot, launch routa-desktop.exe.
  3. App exits before the WebView2 window appears.

Launching with stderr capture:

$env:RUST_BACKTRACE = 'full'
Start-Process -FilePath 'C:\Routa Desktop\routa-desktop.exe' `
  -RedirectStandardOutput stdout.log -RedirectStandardError stderr.log -NoNewWindow

Captured output

stdout (last lines before crash):

[rust-server] Using bundled frontend: \?\C:\Routa Desktop\frontend
[rust-server] Starting embedded Rust backend server
[rust-server] Database path: C:\Users\<user>\AppData\Roaming\com.routa.desktop\routa.db
[rust-server] Static dir: \?\C:\Routa Desktop\frontend
[rust-server] Listening on 127.0.0.1:3210

stderr (panic + stack trace — unsymbolicated, shipped binary):

thread 'main' (8340) panicked at /rustc/59807616e1fa2540724bfbac14d7976d7e4a3860/library\std\src\time.rs:445:33:
overflow when subtracting duration from instant
stack backtrace:
   0:     0x7ff65ceaa6ed - <unknown>
   1:     0x7ff65b39fb11 - <unknown>
   2:     0x7ff65cea9ed4 - <unknown>
   3:     0x7ff65cea9c79 - <unknown>
   4:     0x7ff65ceb0525 - <unknown>
   5:     0x7ff65ceb04cf - <unknown>
   6:     0x7ff65ceb1b4e - <unknown>
   7:     0x7ff65d211c0d - <unknown>
   8:     0x7ff65d211eb5 - <unknown>
   9:     0x7ff65be34065 - <unknown>
  10:     0x7ff65be2faab - <unknown>
  11:     0x7ff65be21147 - <unknown>
  12:     0x7ff65be1bbf6 - <unknown>
  13:     0x7ff65b863352 - <unknown>
  14:     0x7ff65b85fcad - <unknown>
  15:     0x7ff65b85f28d - <unknown>
  16:     0x7ff65b85ee52 - <unknown>
  17:     0x7ff65b8536ad - <unknown>
  18:     0x7ff65b8454ca - <unknown>
  19:     0x7ff65b351016 - <unknown>
  20:     0x7ff65b3510c8 - <unknown>
  21:     0x7ff65d2091d0 - <unknown>
  22:     0x7fff88c3e957 - BaseThreadInitThunk
  23:     0x7fff8a46427c - RtlUserThreadStart
[0522/112257.385:ERROR:ui\gfx\win\window_impl.cc:172] Failed to unregister class Chrome_WidgetWin_0. Error = 1412

The frames between the embedded backend startup (Listening on 127.0.0.1:3210) and the panic suggest the offending code runs as part of bringing up the backend / initializing some time-based state (cache TTL, metrics buckets, deadline, idle-timeout, or similar) right after the listener is bound.

Root-cause hypothesis

std/src/time.rs:445 is the impl Sub<Duration> for Instant path that panics on underflow. On Windows, Instant is backed by QueryPerformanceCounter and is monotonic from boot. So any expression like:

let earlier = Instant::now() - Duration::from_secs(N);

where N exceeds the seconds-since-boot will panic. With ~20 min uptime, any N > ~1200 triggers it; the next time the same machine has been up for >= N seconds, the same call succeeds and the app starts fine.

This is a known footgun in Tokio/Hyper-style code (connection-pool idle timeouts, "x seconds ago" deadline computation, Instant::now() - keepalive, etc.) and in any code that initializes a "last refreshed" marker by subtracting a fixed interval from Instant::now() at startup.

Workaround for users

  • Wait until system uptime exceeds the (unknown but apparently large) duration the app subtracts. Re-launching every ~5 min eventually works; rebooting makes it worse.

Suggested fix

Audit startup-path code (most likely inside the embedded backend / Rust server crate, given the panic happens right after Listening on 127.0.0.1:3210) for Instant - Duration arithmetic. Replace with saturating arithmetic:

let earlier = Instant::now()
    .checked_sub(duration)
    .unwrap_or_else(Instant::now);

or lazy-initialize the value (Option<Instant> / set on first use). Likely places to grep:

  • Instant::now() -
  • .checked_sub( / lack thereof
  • connection-pool / keep-alive / idle-timeout config
  • cache "last refreshed" / "next refresh" bootstrap
  • metrics window initialization

Reproducing on the maintainer side: boot a Windows VM, launch the app within the first few minutes of uptime, and the panic should fire reliably. A symbolicated build (RUST_BACKTRACE=full with debug symbols, or a cargo build of routa-server with panic = \"abort\" disabled and symbols on) will pin the exact callsite.

Happy to provide additional logs or a symbolicated trace if a debug build is published.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions