From b5fd3464379056a7fb8eacb64372d360260b9a3b Mon Sep 17 00:00:00 2001 From: Moe Hamade <69801237+moehamade@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:27:53 +0300 Subject: [PATCH] fix: test the hotspot hold under the lock that publishes Aware The previous recheck narrowed the race without closing it. Testing hotspotHold outside lifecycleLock left this interleaving: 1. the recheck passes, hold not yet set 2. holdForHotspot() sets the flag and calls stop(), which takes the lock, finds nothing published, and returns having stopped nothing 3. this block takes the lock and publishes the service anyway Aware ends up running while the hotspot believes it owns the radio, which is the original failure. The hold is now tested inside the same synchronized block that publishes, so the check and the publication are one step against stop(). Ordering holds because holdForHotspot() sets the flag before calling stop(): either the publisher observes the flag and abandons, or stop() observes the published service and tears it down. There is no interleaving that leaves a service published with the hold set. stopServices() stays outside the lock since it can block. Co-Authored-By: Claude Opus 5 (1M context) --- .../android/wifi-aware/WifiAwareController.kt | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt index f64e1527..ccb8b55f 100644 --- a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt +++ b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt @@ -190,36 +190,39 @@ object WifiAwareController { } startedService.startServices() - // The hotspot can claim the radio at any point during the work above. - // Committing the service now would resurrect NAN behind its back and - // leave every P2P attempt answering BUSY, so drop what we just started. - if (hotspotHold.get()) { - Log.i(TAG, "Hotspot claimed the radio while starting; abandoning Wi-Fi Aware start") - try { startedService.stopServices() } catch (_: Exception) { } - synchronized(lifecycleLock) { + // Test the hold inside the same lock that publishes the service, and that + // stop() takes. Testing it outside leaves a window where holdForHotspot() + // sets the flag and stop() finds nothing published yet, and this block then + // publishes anyway — resurrecting NAN while the hotspot owns the radio. + // Ordering holds because holdForHotspot() sets the flag before calling + // stop(): either we see the flag here, or stop() sees our published service. + val published = synchronized(lifecycleLock) { + val canPublish = !hotspotHold.get() && startedService.isRunning() + if (canPublish) { + service = startedService + _running.value = true + } else { if (service === startedService) service = null _running.value = false } - return + canPublish } - if (startedService.isRunning()) { - synchronized(lifecycleLock) { - service = startedService - _running.value = true - } + if (published) { try { com.bitchat.android.service.MeshServiceHolder.unifiedMeshService?.refreshDelegates() } catch (_: Exception) { } clearBlockedDebugMessage() try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware started")) } catch (_: Exception) {} } else { - if (reusableService == null) { + // stopServices() can block, so keep it out of the lock. + val heldForHotspot = hotspotHold.get() + if (heldForHotspot || reusableService == null) { try { startedService.stopServices() } catch (_: Exception) { } } - synchronized(lifecycleLock) { - if (service === startedService) service = null - _running.value = false + if (heldForHotspot) { + Log.i(TAG, "Abandoned Wi-Fi Aware start: hotspot claimed the radio") } - try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware did not start")) } catch (_: Exception) {} + val detail = if (heldForHotspot) "held down for the hotspot" else "did not start" + try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Wi‑Fi Aware $detail")) } catch (_: Exception) {} } } catch (e: Throwable) { Log.e(TAG, "Failed to start WifiAwareMeshService", e)