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
- Reboot Windows (or have a freshly-booted system with low uptime).
- Within ~20 minutes of boot, launch
routa-desktop.exe.
- 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.
Summary
routa-desktop.execrashes during startup with a Rust panic:The embedded Rust backend reaches the point of binding to
127.0.0.1:3210, then panics before the WebView2 window becomes visible. The trailingFailed to unregister class Chrome_WidgetWin_0. Error = 1412is 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 subtractedDurationexceeds system uptime. On Windows,std::time::Instantis monotonic from boot, so any subtraction that assumes a non-zero baseline will underflow on a freshly-booted system.Environment
C:\Routa Desktop\Reproduction
routa-desktop.exe.Launching with stderr capture:
Captured output
stdout (last lines before crash):
stderr (panic + stack trace — unsymbolicated, shipped binary):
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:445is theimpl Sub<Duration> for Instantpath that panics on underflow. On Windows,Instantis backed byQueryPerformanceCounterand is monotonic from boot. So any expression like:where
Nexceeds the seconds-since-boot will panic. With ~20 min uptime, anyN > ~1200triggers it; the next time the same machine has been up for>= Nseconds, 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 fromInstant::now()at startup.Workaround for users
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) forInstant - Durationarithmetic. Replace with saturating arithmetic:or lazy-initialize the value (
Option<Instant>/ set on first use). Likely places to grep:Instant::now() -.checked_sub(/ lack thereofReproducing 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=fullwith debug symbols, or acargo buildofrouta-serverwithpanic = \"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.