Skip to content

Commit 5e462d2

Browse files
implement sync locks
1 parent cdb6b48 commit 5e462d2

File tree

10 files changed

+81
-45
lines changed

10 files changed

+81
-45
lines changed

demos/supabase-anonymous-auth/pubspec.lock

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ packages:
350350
path: "../../packages/powersync"
351351
relative: true
352352
source: path
353-
version: "1.3.0-alpha.7"
353+
version: "1.3.0-alpha.8"
354354
realtime_client:
355355
dependency: transitive
356356
description:
@@ -479,10 +479,11 @@ packages:
479479
sqlite_async:
480480
dependency: "direct main"
481481
description:
482-
name: sqlite_async
483-
sha256: "7c5a9bec86b6f5b7511b9ba30974fa7ea470aee2dc0d5b7021f6321a439a8d63"
484-
url: "https://pub.dev"
485-
source: hosted
482+
path: "packages/sqlite_async"
483+
ref: "feat/navigator-locks"
484+
resolved-ref: "588d92f3dc98ab2629d2b420ad607639b835214b"
485+
url: "https://github.com/powersync-ja/sqlite_async.dart.git"
486+
source: git
486487
version: "0.8.0"
487488
stack_trace:
488489
dependency: transitive

demos/supabase-anonymous-auth/pubspec.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ dependencies:
1616
supabase_flutter: ^2.0.2
1717
path: ^1.8.3
1818
logging: ^1.2.0
19-
sqlite_async: ^0.8.0
19+
sqlite_async:
20+
git:
21+
url: https://github.com/powersync-ja/sqlite_async.dart.git
22+
ref: feat/navigator-locks
23+
path: packages/sqlite_async
2024
universal_io: ^2.2.2
2125

2226
dev_dependencies:

demos/supabase-edge-function-auth/pubspec.lock

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ packages:
350350
path: "../../packages/powersync"
351351
relative: true
352352
source: path
353-
version: "1.3.0-alpha.7"
353+
version: "1.3.0-alpha.8"
354354
realtime_client:
355355
dependency: transitive
356356
description:
@@ -479,10 +479,11 @@ packages:
479479
sqlite_async:
480480
dependency: "direct main"
481481
description:
482-
name: sqlite_async
483-
sha256: "7c5a9bec86b6f5b7511b9ba30974fa7ea470aee2dc0d5b7021f6321a439a8d63"
484-
url: "https://pub.dev"
485-
source: hosted
482+
path: "packages/sqlite_async"
483+
ref: "feat/navigator-locks"
484+
resolved-ref: "588d92f3dc98ab2629d2b420ad607639b835214b"
485+
url: "https://github.com/powersync-ja/sqlite_async.dart.git"
486+
source: git
486487
version: "0.8.0"
487488
stack_trace:
488489
dependency: transitive

demos/supabase-edge-function-auth/pubspec.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ dependencies:
1616
supabase_flutter: ^2.0.2
1717
path: ^1.8.3
1818
logging: ^1.2.0
19-
sqlite_async: ^0.8.0
19+
sqlite_async:
20+
git:
21+
url: https://github.com/powersync-ja/sqlite_async.dart.git
22+
ref: feat/navigator-locks
23+
path: packages/sqlite_async
2024
universal_io: ^2.2.2
2125

2226
dev_dependencies:

demos/supabase-simple-chat/pubspec.lock

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ packages:
382382
path: "../../packages/powersync"
383383
relative: true
384384
source: path
385-
version: "1.3.0-alpha.7"
385+
version: "1.3.0-alpha.8"
386386
realtime_client:
387387
dependency: transitive
388388
description:
@@ -535,10 +535,11 @@ packages:
535535
sqlite_async:
536536
dependency: transitive
537537
description:
538-
name: sqlite_async
539-
sha256: "7c5a9bec86b6f5b7511b9ba30974fa7ea470aee2dc0d5b7021f6321a439a8d63"
540-
url: "https://pub.dev"
541-
source: hosted
538+
path: "packages/sqlite_async"
539+
ref: "feat/navigator-locks"
540+
resolved-ref: "588d92f3dc98ab2629d2b420ad607639b835214b"
541+
url: "https://github.com/powersync-ja/sqlite_async.dart.git"
542+
source: git
542543
version: "0.8.0"
543544
stack_trace:
544545
dependency: transitive

demos/supabase-todolist/pubspec.lock

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -454,14 +454,14 @@ packages:
454454
path: "../../packages/powersync"
455455
relative: true
456456
source: path
457-
version: "1.3.0-alpha.7"
457+
version: "1.3.0-alpha.8"
458458
powersync_attachments_helper:
459459
dependency: "direct main"
460460
description:
461461
path: "../../packages/powersync_attachments_helper"
462462
relative: true
463463
source: path
464-
version: "0.3.0-alpha.2"
464+
version: "0.3.0-alpha.3"
465465
realtime_client:
466466
dependency: transitive
467467
description:
@@ -590,10 +590,11 @@ packages:
590590
sqlite_async:
591591
dependency: "direct main"
592592
description:
593-
name: sqlite_async
594-
sha256: "7c5a9bec86b6f5b7511b9ba30974fa7ea470aee2dc0d5b7021f6321a439a8d63"
595-
url: "https://pub.dev"
596-
source: hosted
593+
path: "packages/sqlite_async"
594+
ref: "feat/navigator-locks"
595+
resolved-ref: "588d92f3dc98ab2629d2b420ad607639b835214b"
596+
url: "https://github.com/powersync-ja/sqlite_async.dart.git"
597+
source: git
597598
version: "0.8.0"
598599
stack_trace:
599600
dependency: transitive

demos/supabase-todolist/pubspec.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ dependencies:
2020
camera: ^0.10.5+7
2121
image: ^4.1.3
2222
universal_io: ^2.2.2
23-
sqlite_async: ^0.8.0
23+
sqlite_async:
24+
git:
25+
url: https://github.com/powersync-ja/sqlite_async.dart.git
26+
ref: feat/navigator-locks
27+
path: packages/sqlite_async
2428

2529
dev_dependencies:
2630
flutter_test:

packages/powersync/lib/src/database/web/web_powersync_database.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,19 @@ class PowerSyncDatabaseImpl
134134

135135
await isInitialized;
136136

137-
// TODO multitab support
137+
// TODO better multitab support
138138
final storage = BucketStorage(database);
139-
140139
final sync = StreamingSyncImplementation(
141140
adapter: storage,
142141
credentialsCallback: connector.getCredentialsCached,
143142
invalidCredentialsCallback: connector.fetchCredentials,
144143
uploadCrud: () => connector.uploadData(this),
145144
updateStream: updates,
146145
retryDelay: Duration(seconds: 3),
147-
client: FetchClient(mode: RequestMode.cors));
146+
client: FetchClient(mode: RequestMode.cors),
147+
// Only allows 1 sync implementation to run at a time per database
148+
// This should be global (across tabs) when using Navigator locks.
149+
identifier: database.openFactory.path);
148150
sync.statusStream.listen((event) {
149151
setStatus(event);
150152
});

packages/powersync/lib/src/streaming_sync.dart

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:http/http.dart' as http;
44
import 'package:powersync/src/abort_controller.dart';
55
import 'package:powersync/src/exceptions.dart';
66
import 'package:powersync/src/log_internal.dart';
7+
import 'package:sqlite_async/mutex.dart';
78

89
import 'bucket_storage.dart';
910
import 'connector.dart';
@@ -37,14 +38,22 @@ class StreamingSyncImplementation {
3738

3839
SyncStatus lastStatus = const SyncStatus();
3940

41+
final Mutex syncMutex, crudMutex;
42+
4043
StreamingSyncImplementation(
4144
{required this.adapter,
4245
required this.credentialsCallback,
4346
this.invalidCredentialsCallback,
4447
required this.uploadCrud,
4548
required this.updateStream,
4649
required this.retryDelay,
47-
required http.Client client}) {
50+
required http.Client client,
51+
52+
/// A unique identifier for this streaming sync implementation
53+
/// A good value is typically the DB file path which it will mutate when syncing.
54+
String? identifier = "unknown"})
55+
: syncMutex = Mutex(identifier: "sync-${identifier}"),
56+
crudMutex = Mutex(identifier: "crud-${identifier}") {
4857
_client = client;
4958
statusStream = _statusStreamController.stream;
5059
}
@@ -65,8 +74,10 @@ class StreamingSyncImplementation {
6574
await invalidCredentialsCallback!();
6675
invalidCredentials = false;
6776
}
68-
await streamingSyncIteration(abortController: abortController);
69-
// Continue immediately
77+
// Protect sync iterations with exclusivity (if a valid Mutex is provided)
78+
await syncMutex.lock(
79+
() => streamingSyncIteration(abortController: abortController),
80+
timeout: retryDelay);
7081
} catch (e, stacktrace) {
7182
final message = _syncErrorMessage(e);
7283
isolateLogger.warning('Sync error: $message', e, stacktrace);
@@ -110,21 +121,23 @@ class StreamingSyncImplementation {
110121
}
111122

112123
Future<bool> uploadCrudBatch() async {
113-
if ((await adapter.hasCrud())) {
114-
_updateStatus(uploading: true);
115-
await uploadCrud();
116-
return false;
117-
} else {
118-
// This isolate is the only one triggering
119-
final updated = await adapter.updateLocalTarget(() async {
120-
return getWriteCheckpoint();
121-
});
122-
if (updated) {
123-
_localPingController.add(null);
124-
}
124+
return crudMutex.lock(() async {
125+
if ((await adapter.hasCrud())) {
126+
_updateStatus(uploading: true);
127+
await uploadCrud();
128+
return false;
129+
} else {
130+
// This isolate is the only one triggering
131+
final updated = await adapter.updateLocalTarget(() async {
132+
return getWriteCheckpoint();
133+
});
134+
if (updated) {
135+
_localPingController.add(null);
136+
}
125137

126-
return true;
127-
}
138+
return true;
139+
}
140+
}, timeout: retryDelay);
128141
}
129142

130143
Future<String> getWriteCheckpoint() async {

packages/powersync/pubspec.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ dependencies:
1010
flutter:
1111
sdk: flutter
1212

13-
sqlite_async: ^0.8.0
13+
sqlite_async:
14+
git:
15+
url: https://github.com/powersync-ja/sqlite_async.dart.git
16+
ref: feat/navigator-locks
17+
path: packages/sqlite_async
18+
1419
universal_io: ^2.0.0
1520
sqlite3_flutter_libs: ^0.5.15
1621
meta: ^1.0.0

0 commit comments

Comments
 (0)