ActivityWatch / ActivityWatch/aw-android

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

Aperta
#220 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
Kotlin
Stelle
265
Fork
57
Merge medio
2g 21h
PR unite (30g)
19

Descrizione

## 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

- Device: OnePlus Nord CE 3 5G (`CPH2493` / `OP556FL1`), arm64
- Android 16 (`BP2A.250605.015`)
- Built from `master` @ `53a2fb6` (includes #204 and #209), debug variant
- `libaw_sync.so` / `libaw_server.so` taken unmodified from the CI `Build` workflow artifacts

## Steps to reproduce

1. Install a build containing #204 + #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///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 `//test.db`, and on Android the observed tree is `///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.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.