Repository navigation
fix(server): don't crash at startup when the server socket can't be created - #315
Conversation
…reated The port probe in startServerTask() only caught BindException. On GrapheneOS with the app's Network permission off, socket() fails with EACCES, surfacing as a plain SocketException (BindException's parent), which escaped the IO coroutine in BackgroundService and killed the app. Extract the probe into probeServerPort() returning FREE/IN_USE/ SOCKET_DENIED and log an actionable error instead of crashing. Git-Session-Id: 8192
|
🤖 AI code reviewSafe to merge — no P0/P1 findings on latest reviewUpdated after inline dispositions on finding threads — this is the current state; the verdict below is frozen at review time and is kept as the historical record of that pass. Finding disposition
The PR refactors the server-port probe in RustInterface.kt into a new probeServerPort() function that returns a PortProbe enum (FREE, IN_USE, SOCKET_DENIED), catching BindException, IOException, and SecurityException. startServerTask() now switches on the probe result and returns early for IN_USE and SOCKET_DENIED instead of only catching BindException. A new test file ServerPortProbeTest.kt covers the probe's four outcomes. Needs a look — P2 onlyConfidence 4/5 1 finding ·
|
Fixes a startup crash on GrapheneOS when the app's Network permission is off. Related reports: ActivityWatch/activitywatch#1003, ActivityWatch/activitywatch#1180, #124.
Root cause
RustInterface.startServerTask()probes the server port with aServerSocketbefore starting Rust, but it only catchesBindException. With GrapheneOS's per-app Network permission revoked,socket()fails with EACCES. That surfaces as a plainSocketException, the parent ofBindException, so it escapes. The probe runs inside theDispatchers.IOcoroutine inBackgroundService.onStartCommand, which has no exception handler, so the uncaught exception kills the app at launch.The probe now returns
FREE/IN_USE/SOCKET_DENIED. A denied socket logs an actionable error ("check the app's Network permission") and the server is not started, instead of the app crashing.How the older reports fit
Java_..._RustInterface_startServer, i.e. a panic aborting at theextern "C"boundary. Before fix(android): don't let Rust panics abort the app across the JNI boundary aw-server-rust#681,start_serverdropped the result oflaunch().await. Rocket 0.5.1'simpl Drop for Errorpanics when an error was never inspected, so any bind failure became an abort. A commenter there also had to enable the Network permission. fix(android): don't let Rust panics abort the app across the JNI boundary aw-server-rust#681 (shipped in v0.14.x) now catches those panics. This PR closes the Kotlin-side path that remained.com.android.chromedisabled, the app starts and runs normally. The app has no hard Chrome dependency, only the WebView provider.JNI DETECTED ERROR: use of invalid jobject 0x8on 8.1): a jstring-returning native returning garbage. That matches a panic unwinding throughextern "C"on pre-1.81 rustc (UB), the same class fix(android): don't let Rust panics abort the app across the JNI boundary aw-server-rust#681 fixed. I could not reproduce it without an arm64 8.1 device.Testing
ServerPortProbeTest: free port, port held by another socket (a realServerSocket),SocketException(EACCES), andSecurityException. I ran it locally with:mobile:testStandardDebugUnitTest.guard fill free_track verify_pointers, as a stand-in for hardened_malloc), and under CheckJNI. None of these produce allocator or JNI errors.Not in this PR: a user-facing "Network permission required" screen. Currently the denial is only logged, and the UI shows the usual server-unreachable state.
Reproduce
java.net.SocketExceptionfrom the port probe inRustInterface.startServerTaskkills the app at launch. With this PR the app starts and logcat shows the "check the app's Network permission" error. (I have no GrapheneOS device, so this path follows from the code above and the reports; I have not run it myself.)INTERNETis an install-time permission there and can't be revoked, so the device path needs GrapheneOS../gradlew :mobile:testStandardDebugUnitTest --tests '*ServerPortProbeTest'.socketCreationDeniedDoesNotEscapeinjects the EACCESSocketException, the same exception GrapheneOS raises.