Correctness and performance issues in watchers, sync and workers
メンテナーはふだん 2 日以内に返信
@0xbrayo がすでに取り組んでいます。
2026年10月8日 から。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 15/100
調査の方向性
This is a tracking issue, not a single task: each checklist item names its own file and fix PR (#334 to #351), so pick one item and go to its named code, such as SessionEventWatcher's SimpleDateFormat parse or SyncScheduler. Start by reading that file and reproducing the described behaviour with its test or a log line. Done means that item's linked fix PR is merged and its checkbox is ticked; the Rust JNI item (#801) depends on an upstream change, so avoid it.
索引モデルが issue の本文から書いたものです。
説明
An audit of the app's watchers, sync, workers and widget found the correctness and performance issues below. Each item will be fixed in its own PR, linked next to it.
Correctness
-
Session watcher replays all history when the last event's timestamp has no fractional seconds. Fix: #334.
SessionEventWatcher(and the legacyUsageStatsWatcherpath) parse the last stored event's timestamp withSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"). aw-server-rust (chrono) omits the fraction when it is zero (2026-10-08T10:00:00Z), which throwsParseException. The watcher then treats the bucket as empty, queries usage events from 0, and every replayed session is inserted again because the heartbeat merge refuses events older than the last one. Roughly 1 in 1000 sessions starts on a whole second. -
Unlock events are duplicated on every ingest run. Fix: #335. Unlocks are re-read from the start of the last session rather than from the unlock bucket's own last event. Every run re-sends all unlocks in that window; only the newest merges, the older ones are inserted again, so unlock counts inflate with each hourly alarm, app open and widget refresh.
-
Two datastores can be opened concurrently in the JNI layer. Fix: ActivityWatch/aw-server-rust#801 (needs an
aw-server-rustsubmodule bump here once merged).openDatastore()inaw-server/src/android/mod.rsinitialises an unsynchronisedstatic mut DATASTORE. Several threads call it at startup (WebWatcher, MediaWatcher, SessionEventWatcher, the server thread, migrations), so twoDatastore::newcalls can race, giving two workers with separate SQLite connections and separate heartbeat caches. -
Sessions of 4 hours or more are silently dropped. Fix: #336.
SessionParserdiscards any session at or aboveMAX_REASONABLE_SESSION_DURATION(4h), losing genuine long sessions (navigation, video, reading). The cap exists to guard against missing PAUSE events, but the screen turning off is a better bound. -
The in-progress foreground session is never counted until it ends. Fix: #337. The parser does not emit the trailing open session, so a 2-hour session in one app is invisible to the dashboard, widget and alerts until the user leaves it.
-
Category alerts are evaluated against stale data. Fix: #338.
NotifyWorkerqueries category time without first ingesting new usage events, so alerts lag by up to the hourly (inexact) ingest alarm. -
Cancelling a sync allows a second concurrent sync. Fix: #339.
SyncInterface.cancel()clears the sharedsyncInFlightflag immediately, but interrupting the executor does not stop the nativesyncBoth, so a new sync can start while the old one is still running. -
Sync runs twice per 15-minute interval. Fix: #340.
SyncScheduler's Handler chain and the AlarmManager fallback both fire every 15 minutes;syncInFlightonly prevents overlap, so each cycle can do two full syncs and two SAF mirrors. A stop/start while a sync is in flight can also leave two Handler chains running. -
RustInterface.serverStartedis not volatile. Fix: #341. It is written on the main thread and polled in a loop by the hostname-rewrite thread, which may never observe the change. -
Browser sessions are split by the keyboard and other overlay windows. Fix: #342.
WebWatcherends the session on any event from a different window id, so typing in the browser (IME window) or a popup ends and restarts the visit. -
Media events are not merged when two players are active. Fix: #343. With two media sessions playing, heartbeats for different data alternate in one bucket, so they never merge and each 15s poll inserts new zero-duration events.
-
The app can crash with SIGBUS on first launch. BackgroundService rewrites bucket hostnames in
sqlite.dbthrough Android's SQLite while the Rust datastore, which bundles its own SQLite, may already have the same WAL database open (watchers open it over JNI before the server starts; the only guard isserverStarted). Each library resizes the shared-shmmapping under the other and the process dies withBUS_ADRERRinwalIndexAppend. This is the intermittentTest release variant (R8 smoke)CI failure. Fix: #354.
Performance
- WebWatcher writes events on the accessibility main thread. Fix: #344.
logBrowserEventcalls the blocking JNIheartbeaton the service's main thread, the same pattern that caused thecreateBucketANR in #261. - WebWatcher walks the accessibility tree on every content-change event. Fix: #345. Every browser
typeWindowContentChangedevent runsfindWebView(up to 2000 binder calls), including for Firefox where it can never match. -
BackgroundService.onCreateconstructsRustInterfaceon the main thread. Fix: #346.loadLibrary+ JNI initialise run on the main thread during service creation, which #262 moved off-thread everywhere else. - The SAF mirror rewrites every file on every sync. Fix: #347.
mirrorDirectorycopies all files in full regardless of whether they changed, sotest.dbis rewritten every 15 minutes. - Session ingest does redundant work per run. Fix: #348. One
getApplicationInfobinder call per session (no cache), two full passes overqueryEvents(sessions and unlocks), andcreateBucketHelperserialises every bucket on each of its two calls. - The widget runs the full-day query once per widget instance. Fix: #349.
updateAllWidgetsre-queries and re-reads settings for every placed widget instead of once. - NotifyWorker's preferences grow without bound. Fix: #350. A new
triggered_<category>_<hash>_<date>key is written per alert per day and never removed; SharedPreferences loads the whole file on each access. - The share-sheet export fallback loads the whole export into memory on the UI thread. Fix: #351.
shareExportcallsreadContent(), undoing the streaming from #304 on that path.
- 主要言語
- Kotlin
- スター
- 277
- フォーク
- 61
- 平均マージ
- 3日 11時間
- マージ済み PR(30日)
- 38
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
ActivityWatch/aw-android のほかの issue
-
Web watcher: add support for Brave (`com.brave.browser` missing from KNOWN_BROWSER_PACKAGES)対応中かも @NikitaMGrimm が 25 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
ActivityWatch/aw-android#306 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 2 日以内に返信
-
AuthSettingsActivity crashes on launch (InflateException: Material attributes under AppCompat theme)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
ActivityWatch/aw-android#210 · コメント 11 件 · リアクション 1 件 ·
メンテナーはふだん 2 日以内に返信
-
Incorrect hostname in `/api/0/info` breaks "this device" badge in Web UI対応中かも @TimeToBuildBob が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 35/100
ActivityWatch/aw-android#368 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
CI: emulator jobs (Test E2E, R8 smoke) fail non-deterministically — red run gives no code signalオープン
難易度 4/5 3〜5日 初心者へのやさしさ 30/100
ActivityWatch/aw-android#332 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 35/100
ActivityWatch/aw-android#302 ·
メンテナーはふだん 2 日以内に返信
ActivityWatch/aw-android の issue をすべて見る
似ている issue
-
documentation importer size:S
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
enorm-labs/event-junkie#3614 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
InsertKoinIO/koin#2477 ·
-
[Submission] AI服务引擎オープンsubmit-adaption submit-adaption-pre
難易度 1/5 1時間未満 初心者へのやさしさ 76/100
BetterAndroid/android-notification-icon-project#783 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
coding size:s skill:kotlin-core sub-issue
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
SKaiNET-developers/SKaiNET#1421 ·
メンテナーはふだん 1 日以内に返信