Skip to content

No iteration is active still reachable after #410: _nonLineSyncEvents senders are not gated on an active iteration #453

Description

@xyyyy

Summary

We see recurring SqliteException(21): powersync_control: invalid state: No iteration is active
on Android, on SDK versions that already contain the control-flow refactor from #410
(we have run 2.1.0, 2.3.0 and 2.3.1).

What we observe

  • Android only. Our web build never produced this error.
  • Across 13 devices, independent of app build and independent of upload health.
  • Correlates with two conditions: access token expiry (we log JWT has expired followed by a burst
    of these errors within ~2s), and network churn (one device logged 3522 network failures over 7 days).
  • Usually transient and self-healing within seconds. We have also recorded sustained episodes —
    longest 115 minutes. During those, downloads stop while local writes and uploads keep working,
    so the device stops seeing other devices' changes.
  • disconnect() + connect() does not recover it. One device logged 92 reconnects over 2 hours
    with lastSyncedAt frozen throughout. A full process restart does recover it.

What we found in the source

In powersync-sqlite-core (crates/core/src/sync/streaming_sync.rs) the core moves itself to
ClientState::Idle both on any error returned from handle.run() and on normal completion —
run() returns done = true in the same invocation in which it pushes Instruction::CloseSyncStream.
Any subsequent SyncEvent then produces this error. Same in core v0.4.13 and v0.5.x.

In the Dart client, two senders push into sync._nonLineSyncEvents — a broadcast controller owned by
StreamingSyncImplementation, so it outlives individual iterations — guarded only by !sync.aborted:

  • packages/powersync/lib/src/sync/streaming_sync.dart (2.3.1, ~L264): _uploadAllCrud().whenComplete
    adds UploadCompleted. The CRUD loop runs concurrently with iterations and is driven by local writes.
  • same file, ~L581: FetchCredentials(didExpire: false) starts a fire-and-forget
    prefetchCredentials().then((_) { if (!sync.aborted) _nonLineSyncEvents.add(TokenRefreshComplete()); }),
    whose resolution time depends on the network.

Environment

  • powersync 2.1.0 / 2.3.0 / 2.3.1 (Flutter), core extension v0.4.13
  • Flutter 3.41.9 (Dart 3.11.5), Android, Samsung SM-T500 tablets and similar
  • Backend: Supabase Postgres + PowerSync Cloud

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