Skip to content

db.connect()/disconnect() issues: db.prop and db.syncStatus.prop values, delayed replication, and read your own writes anomaly #226

Open
@nurturenature

Description

Hi,

When db.disconnect()/connect()ing, some issues have been observed.

The issue is being opened against the Dart client, even though it's likely to include other libs, as that's where the behavior is being observed.

Using debugging code like:

log.fine('/powersync/$action: request');
...
switch (action) { 
  case 'connect':
    await db.connect(connector: connector);
    response = {
      'db.closed': db.closed,
      'db.connected': db.connected,
      'db.currentStatus': '${db.currentStatus}'
    };
    break;
  case 'disconnect':
    await db.disconnect();
    response = {
      'db.closed': db.closed,
      'db.connected': db.connected,
      'db.currentStatus': '${db.currentStatus}'
    };
    break;
}
...
log.fine('/powersync/$action: response: $response');

The expectation is that on return from await:

  • when connecting, both db.connected and db.syncStatus.connected with be true
  • db.syncStatus.uploading/downloading will be false if db.connected and db.syncStatus.connected are false

It's also possible for db.disconnect()/connect() to lead to replication pausing for 30s+:

  • even when there's no database activity
  • all local writes have been uploadDatad

And of most concern, db.disconnect()/connect() can lead to:

The first two issues can hopefully be debugged with existing or straightforward tests.
If the last issue isn't just another way to express the sqlite-core reverting local changes, I can pursue some type of simpler test case than a full Jepsen test.

Thanks!

Tests disconnect() a random majority of nodes every ~5s for ~5s, and the connect() all nodes.
Some annotated sample log snippets, search for '#' for annotation comments.

SyncStatus not always tracking/reflecting the actual state:

[2025-01-09 00:06:53.097713] [powersync_endpoint] [FINE] /powersync/connect: request

# suspect? connected is false even after awaiting
[2025-01-09 00:06:53.097766] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>
[2025-01-09 00:06:53.097825] [powersync_endpoint] [FINE] /powersync/connect: response: {db.closed: false, db.connected: false, db.currentStatus: SyncStatus<connected: false connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>}
[2025-01-09 00:06:53.099358] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: false lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>

# suspect? connected is false, yet uploading is true
[2025-01-09 00:06:53.099477] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: true lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>
[2025-01-09 00:06:53.102568] [powersync_endpoint] [FINER] uploadData: (2) patch result: {id: d5dbe3f3-5ca9-4327-958d-7a3daa154482, k: 7, v:  1}
[2025-01-09 00:06:53.111988] [powersync_endpoint] [FINER] uploadData: (3) patch result: {id: f5ce635c-7231-475e-bc28-4ca6517283e3, k: 9, v:  1 8}
[2025-01-09 00:06:53.112972] [powersync_endpoint] [FINER] uploadData: (3) patch result: {id: 4a59b53a-badd-4333-8fa9-b4c8095c7eba, k: 8, v:  1 7}
[2025-01-09 00:06:53.117300] [powersync_endpoint] [FINER] uploadData: (4) patch result: {id: f5ce635c-7231-475e-bc28-4ca6517283e3, k: 9, v:  1 8 10}
[2025-01-09 00:06:53.118872] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: true lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>
[2025-01-09 00:06:53.118922] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: false lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>

# connect finally becomes true, but only after uploading is complete, e.g. clears the upload queue that was accumulated during the previous disconnect
[2025-01-09 00:06:53.133736] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:06:50.197767, hasSynced: true, error: null>

replication paused

# at the end of a test run, a majority of nodes look like

# suspect? insure connected, but note the disconnect bounce
[2025-01-09 00:08:30.584932] [powersync_endpoint] [FINE] /powersync/connect: request
[2025-01-09 00:08:30.584981] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.585034] [powersync_endpoint] [FINE] /powersync/connect: response: {db.closed: false, db.connected: false, db.currentStatus: SyncStatus<connected: false connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>}
[2025-01-09 00:08:30.586498] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.586722] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: true lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>

# suspect? we're uploading even though not connected was/is/remains false
[2025-01-09 00:08:30.589472] [powersync_endpoint] [FINER] uploadData: (119) patch result: {id: 1f32b853-b006-4b78-8ad2-16e9c238a5a8, k: 11, v:  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 18 23 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 99 100 101 102 103 104 105 106 107 108 110 111 113 114 115}
[2025-01-09 00:08:30.592236] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: true lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.592272] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: false connecting: true downloading: false uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>

# let's explicitly wait for upload queue count == 0
[2025-01-09 00:08:30.600952] [powersync_endpoint] [FINE] /powersync/upload-queue-wait: request
[2025-01-09 00:08:30.601770] [powersync_endpoint] [FINE] /powersync/upload-queue-wait: response: {db.upload-queue-wait: queue-empty}
[2025-01-09 00:08:30.602277] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.602372] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.604702] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.604726] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:08:30.606861] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>

# let's explicitly wait for db.syncStatus.downloading to become false
# suspect? we wait for 30 seconds before logging an error and continuing
# suspect? even though all test activity has stopped
# suspect? it will be 30s+ before we get an UpdateNotification<{lww}> and db.syncStatus.downloading becomes false
[2025-01-09 00:08:30.607448] [powersync_endpoint] [FINE] /powersync/downloading-wait: request
[2025-01-09 00:09:00.638623] [powersync_endpoint] [FINE] /powersync/downloading-wait: response: {ERROR: Tried 299 times every 0:00:00.100000, db.currentStatus: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>}

[2025-01-09 00:09:00.638968] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.638987] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.639005] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.639018] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.639263] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.639948] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.641163] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.641186] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.641251] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.641265] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.641764] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: true uploading: false lastSyncedAt: 2025-01-09 00:08:30.100527, hasSynced: true, error: null>
[2025-01-09 00:09:00.642903] [powersync_endpoint] [FINEST] updates: UpdateNotification<{lww}>
[2025-01-09 00:09:00.643012] [powersync_endpoint] [FINEST] statusStream: SyncStatus<connected: true connecting: false downloading: false uploading: false lastSyncedAt: 2025-01-09 00:09:00.642986, hasSynced: true, error: null>

# final reads occur on all nodes and are consistent

client not reading its own previous write

read your writes anomaly

The transaction graph shows a cycle:

  • p-> is process order

    • the top transaction happened first
      • appended '40' to key 17
      • read key 17 and it shows the append of '40'
    • the bottom transaction happened later in the same process
      • did not observe the append of '40' to key 17
  • rw-> is logical read/write order

    • it shows the bottom transaction would have logically happened first

So the graph cycles which shows a ready your own writes anomaly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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