Skip to content

Sync aborts the app process (SIGABRT in libaw_sync.so) on Android 16 — syncBoth and syncPush alike #220

Description

@Lorite

Summary

With sync enabled, the app is killed by SIGABRT roughly half a second into every sync cycle. The abort happens inside libaw_sync.so, so performSyncAsync never returns from syncFn() and the SAF mirror added in #209 is never reached — the sync directory is populated internally but nothing is ever copied to the user-chosen SAF directory.

This affects both JNI entry points: syncBoth and syncPush abort identically, so it is not specific to the pull path.

Environment

Steps to reproduce

  1. Install a build containing feat(sync): add SyncSettingsActivity — enable/disable toggle + SAF directory picker #204 + feat(sync): bridge SAF-granted URI to aw-sync output (Phase 2) #209.
  2. Open Sync Settings, enable the toggle, choose an SAF directory in shared storage (e.g. /sdcard/ActivityWatchSync).
  3. Wait ~1 minute for SyncScheduler's first sync.

Observed

08:04:29.565 I SyncInterface: Starting sync operation: Full Sync
08:04:30.277 F DEBUG   : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
08:04:30.277 F DEBUG   : pid: 25108, tid: 25696, name: pool-6-thread-1
08:04:30.277 F DEBUG   :   #00 pc 000000000008b70c  libc.so (abort+156)
08:04:30.277 F DEBUG   :   #01 pc 00000000012eb124  base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG   :   #02 pc 00000000012e7d5c  base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG   :   #03 pc 00000000012d8c80  base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG   :   #04 pc 00000000012e81f8  base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG   :   #05 pc 00000000012d7fa4  base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG   :   #15 pc 00000000006d4c28  base.apk (Java_net_activitywatch_android_SyncInterface_syncBoth+260)
08:04:30.277 F DEBUG   :   #25 ... SyncInterface$syncBothAsync$2.invoke
08:04:30.277 F DEBUG   :   #33 ... SyncInterface.performSyncAsync$lambda$4

Patching SyncScheduler.performSync() to call syncPushAsync instead produces the same abort, twice in one capture:

08:14:10.059 I SyncInterface: Starting sync operation: Push
08:14:10.579 F libc    : Fatal signal 6 (SIGABRT) in tid 8024 (pool-6-thread-1)
             #15 ... (Java_net_activitywatch_android_SyncInterface_syncPush+260)
08:15:23.946 I SyncInterface: Starting sync operation: Push
08:15:24.024 F libc    : Fatal signal 6 (SIGABRT) in tid 9839 (pool-5-thread-1)
             #15 ... (Java_net_activitywatch_android_SyncInterface_syncPush+260)
08:15:24.528 I Zygote  : Process 8448 exited due to signal 6 (Aborted)

Note both entry points abort at the same +260 offset.

What this narrows it down to

The staging tree is created before the crash:

files/sync/<hostname>/<device-id>/test.db   45056 bytes

In aw-sync/src/sync.rs, sync_run fetches info from the local server, takes info.device_id, and only then calls setup_local_remote(path, device_id). Since the directory is named with the correct device id, both the get_info() call against the in-app aw-server-rust and the datastore creation must have succeeded. That places the abort downstream of both, in the bucket-sync step itself rather than in startup, the HTTP client, or directory setup.

SIGABRT with no fault address is consistent with a Rust panic, but the panic message never reaches logcat (Rust writes panics to stderr, which Android discards), and the shipped .so has no .debug_info, so addr2line only resolves to the nearest exported symbol. A debug build, or routing the Rust panic hook to android_log, would identify the frame immediately.

Suggested follow-ups

  1. Install a panic hook that logs via android_log so panics are diagnosable from logcat at all — useful well beyond this bug.
  2. Publish a symbol-bearing build (or upload symbols) so tombstones can be symbolicated.

Secondary observations

Two smaller things found while investigating, happy to open separate issues or PRs if useful:

SyncSettingsActivity is unreachable from the UI. It is registered only in res/menu/main.xml (the options menu), but MainActivity never calls setSupportActionBar(), the theme is AppTheme.NoActionBar, and the Toolbar in res/layout/app_bar_main.xml has been commented out since 4a02a0b6 (2018). So onCreateOptionsMenu inflates a menu with no host and no overflow button is rendered, while activity_main_drawer.xml has no sync entry. Adding a nav_sync_settings item to the drawer (mirroring nav_auth_settings) makes it reachable.

The SAF mirror in #209 copies nothing even when sync succeeds. copySyncFilesToSafDir() does a flat File(syncDir).listFiles()?.filter { it.isFile }, but aw-sync never writes a regular file at the root of the sync directory — setup_local_remote writes <sync_dir>/<device_id>/test.db, and on Android the observed tree is <sync_dir>/<hostname>/<device_id>/test.db. So the filter matches zero entries. The receiving side also requires the nesting: find_remotes in aw-sync/src/util.rs keeps only directories and looks for *.db one level inside them, so a flattened copy would be ignored even if it were made. A recursive, structure-preserving mirror fixes both halves.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions