From ab045c406db5acf65bd269364225a2dbf69a3c84 Mon Sep 17 00:00:00 2001 From: pinoybear <22431440+pinoybear@users.noreply.github.com> Date: Sat, 15 Aug 2026 08:49:30 -0600 Subject: [PATCH] android: force Client.app to initialize before executing local API requests Client's `app` property was added in #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> --- .../main/java/com/tailscale/ipn/ui/localapi/Client.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt b/android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt index aeed568aca..4ce4bf7deb 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/localapi/Client.kt @@ -71,6 +71,17 @@ class Client(private val scope: CoroutineScope) { // Access libtailscale.Application lazily private val app: libtailscale.Application by lazy { App.get().getLibtailscaleApp() } + init { + // Force the lazy `app` property to evaluate now, at construction time, instead of + // waiting for something to read it. This guarantees App.get() (and therefore + // Request.setApp()) has run before this Client can be used to execute a Request, + // even if the very first caller is a background WorkManager task on a cold-started + // process that never otherwise touches App.get(). Without this, `app` was declared + // but never read anywhere in this class, so it never actually forced initialization + // (see #563, which added this property with that intent but never wired it up). + app + } + fun start(options: Ipn.Options, responseHandler: (Result) -> Unit) { val body = Json.encodeToString(options).toByteArray() return post(Endpoint.START, body, responseHandler = responseHandler)