Skip to content

Fix root service connection race condition#1496

Merged
lihenggui merged 3 commits intomainfrom
fix/root-service-connection-race-condition
Mar 14, 2026
Merged

Fix root service connection race condition#1496
lihenggui merged 3 commits intomainfrom
fix/root-service-connection-race-condition

Conversation

@lihenggui
Copy link
Copy Markdown
Owner

Summary

  • Add Mutex to RootServiceConnection.ensureConnected() to prevent concurrent bind attempts that cause neither onServiceConnected callback to fire
  • Add auto-reconnect in RootApiController.switchComponent() when root service is disconnected

Problem

During app startup, AppPermissionMonitor.permissionStatus Flow emits the controller type twice in quick succession (~65ms apart), causing two concurrent calls to ensureConnected(). Both calls see service == null and both invoke RootService.bind() with different ServiceConnection instances. The second bind interferes with the first, resulting in neither onServiceConnected callback being triggered. This leaves rootService permanently null, causing all subsequent component control operations to fail with RootUnavailableException.

Test plan

  • Build and install debug APK on a rooted device
  • Verify root controller initializes successfully on app startup (check for "onServiceConnected" in logs)
  • Verify component enable/disable operations work in PM mode
  • Verify no duplicate "Binding to RootServer" log entries during startup

…eption

Add Mutex to RootServiceConnection.ensureConnected() to prevent concurrent
bind attempts. During app startup, AppPermissionMonitor could call
ensureConnected() twice in quick succession, both seeing service==null and
both calling RootService.bind() with different ServiceConnection instances.
The second bind interfered with the first, causing neither onServiceConnected
callback to fire.

Also add auto-reconnect in RootApiController.switchComponent() so that if
the root service disconnects after initialization, it attempts to reconnect
before throwing RootUnavailableException.
The permissionStatus Flow in AppPermissionMonitor was a cold Flow, meaning
each collector independently triggered the entire chain including
initController(). During app startup, multiple collectors (BlockerAppState,
AppListViewModel) would each trigger RootService.bind() concurrently with
different ServiceConnection instances. The second bind interfered with the
first, causing neither onServiceConnected callback to fire, leaving
rootService permanently null.

Fix by converting permissionStatus to a hot Flow using shareIn() with
application scope, so initController() executes only once regardless of how
many collectors subscribe. Also revert the Mutex approach which made things
worse by permanently blocking all callers when the first bind hung.
RootServer.onBind() used EntryPointAccessors.fromApplication() to obtain
PackageInfoDataSource via Hilt, but the root service runs in a separate
process (com.merxury.blocker.debug:root:0) started by libsu's app_process
which has no Hilt-initialized Application context. This caused
IllegalStateException on every bind attempt, preventing onServiceConnected
from ever being called.

Fix by removing the Hilt EntryPoint dependency and directly using
ApplicationUtil.isSystemApp() instead, which was the only method from
PackageInfoDataSource actually used in the root process.
@lihenggui lihenggui merged commit 465a78f into main Mar 14, 2026
4 checks passed
@lihenggui lihenggui deleted the fix/root-service-connection-race-condition branch March 14, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant