android: force Client.app to initialize before executing local API requests - #839
android: force Client.app to initialize before executing local API requests#839pinoybear wants to merge 1 commit into
Conversation
Please elaborate. Did you compile & test this change on your own device or not? |
…quests Client's `app` property was added in tailscale#563 to force App.get() (and therefore Request.setApp()) to run before any local API call, but `by lazy` only evaluates on first read, and nothing in Client ever reads `this.app`. The property has been dead code since it was introduced, so this never actually prevented the race it was meant to prevent. If a Client is constructed and used before anything else has called App.get() -- e.g. a WorkManager background task on a cold-started process -- Request.execute() reads the companion object's separate lateinit var app before Request.setApp() has run, and crashes with "lateinit property app has not been initialized". Add an init block that reads app, forcing the lazy initializer at construction time. App.initOnce() is @synchronized, so this blocks the constructing thread until initialization has completed. This brings Client in line with Notifier.start(), which already guards the equivalent case correctly. Fixes tailscale/tailscale#20884 Updates tailscale/tailscale#14125 Updates tailscale/tailscale#14314 Signed-off-by: pinoybear <22431440+pinoybear@users.noreply.github.com>
d49c0c0 to
ab045c4
Compare
|
Correct — I have not compiled or run this on a device. This was static analysis: full rename-aware commit history of The fix itself is a one-line addition ( |
What this fixes
Fixes the
lateinit property app has not been initializedcrash atRequest.execute(), most recently reported in tailscale/tailscale#20884, and previously in #14125 / #14314.Root cause
Clienthas anappproperty intended to forceApp.get()(and thereforeRequest.setApp()) to run before any local API call:This property was added in #563 specifically to prevent this crash ("Lazily init app in Client to ensure that we aren't trying to make any local API calls before app has been initialized"). However,
by lazyonly evaluates on first read of the property, and nothing inClientever readsthis.app— none ofstart(),status(),prefs(), etc. reference it. So the property has never actually forced initialization since it was introduced; it's been dead code from the start (confirmed by checking every commit that has touched this file since #563 — none add or remove a reference toapp).If a
Clientis constructed and used before anything else has calledApp.get()— e.g. a WorkManager background task running on a cold-started process —Request.execute()reads the companion object's separatelateinit var app(set only viaRequest.setApp(), called fromApp.startLibtailscale()) before it's been set, and crashes.Fix
Add an
initblock that readsapp, forcing the lazy initializer to run atClientconstruction time rather than never.App.initOnce()is@Synchronized, so this blocks the constructing thread untilApp.get()(andRequest.setApp()) has fully completed, closing the race.Client.ktis the only placeRequestis constructed in this codebase, so this covers every path that can reach this crash today.This isn't a novel pattern —
Notifier.start()already guards the equivalent case correctly:This PR brings
Clientin line with that existing, correct approach used elsewhere in this codebase for the same class of problem.Verification
Static/manual verification only — I don't have a full Android build environment to run
make fmt-check/make tailscale-debug.apklocally, so please treat CI as the source of truth for formatting and build correctness. Happy to address any CI failures.Confirmed via
adb shell dumpsys dropboxthat this exact crash signature is currently reproducing on a shipped 1.102.2 build (Galaxy Z Fold8, and previously a Fold7 on a different Android major version), despite the crash being reported "resolved" after #563/#14125. Full crash details and device history in tailscale/tailscale#20884.