Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Correctness and performance issues in watchers, sync and workers

Aperta
#333 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

@0xbrayo ci sta già lavorando.

Dal 8/10/2026.

  • #339 di @0xbrayo — aperta
  • #342 di @0xbrayo — aperta
  • #350 di @0xbrayo — aperta

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
15/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
android, kotlin, rust
Ambito
mobile

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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 legacy UsageStatsWatcher path) parse the last stored event's timestamp with SimpleDateFormat("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 throws ParseException. 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-rust submodule bump here once merged). openDatastore() in aw-server/src/android/mod.rs initialises an unsynchronised static mut DATASTORE. Several threads call it at startup (WebWatcher, MediaWatcher, SessionEventWatcher, the server thread, migrations), so two Datastore::new calls can race, giving two workers with separate SQLite connections and separate heartbeat caches.

  • Sessions of 4 hours or more are silently dropped. Fix: #336. SessionParser discards any session at or above MAX_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. NotifyWorker queries 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 shared syncInFlight flag immediately, but interrupting the executor does not stop the native syncBoth, 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; syncInFlight only 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.serverStarted is 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. WebWatcher ends 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.db through 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 is serverStarted). Each library resizes the shared -shm mapping under the other and the process dies with BUS_ADRERR in walIndexAppend. This is the intermittent Test release variant (R8 smoke) CI failure. Fix: #354.

Performance
  • WebWatcher writes events on the accessibility main thread. Fix: #344. logBrowserEvent calls the blocking JNI heartbeat on the service's main thread, the same pattern that caused the createBucket ANR in #261.
  • WebWatcher walks the accessibility tree on every content-change event. Fix: #345. Every browser typeWindowContentChanged event runs findWebView (up to 2000 binder calls), including for Firefox where it can never match.
  • BackgroundService.onCreate constructs RustInterface on 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. mirrorDirectory copies all files in full regardless of whether they changed, so test.db is rewritten every 15 minutes.
  • Session ingest does redundant work per run. Fix: #348. One getApplicationInfo binder call per session (no cache), two full passes over queryEvents (sessions and unlocks), and createBucketHelper serialises every bucket on each of its two calls.
  • The widget runs the full-day query once per widget instance. Fix: #349. updateAllWidgets re-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. shareExport calls readContent(), undoing the streaming from #304 on that path.
Lingua principale
Kotlin
Stelle
279
Fork
63
Merge medio
4g 22h
PR unite (30g)
57

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ActivityWatch/aw-android

Tutte le issue di ActivityWatch/aw-android

Issue simili

Altre issue su Kotlin

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.