Skip to content

Commit c1b3c4e

Browse files
authored
Merge pull request #98 from powersync-ja/release-tab-lock-on-close
Shared worker: Release mutex when tab closes
2 parents 4b6c1b7 + cfdb89c commit c1b3c4e

File tree

5 files changed

+109
-0
lines changed

5 files changed

+109
-0
lines changed

packages/sqlite_async/build.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
targets:
2+
$default:
3+
builders:
4+
build_web_compilers:entrypoint:
5+
options:
6+
# Workers can't be compiled with dartdevc, so use dart2js for the example
7+
compiler: dart2js
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
3+
<html>
4+
<head>
5+
<meta charset="utf-8">
6+
<title>sqlite_async web demo</title>
7+
<script defer src="main.dart.js"></script>
8+
</head>
9+
10+
<body>
11+
12+
<h1>sqlite_async demo</h1>
13+
14+
<main>
15+
This page is used to test the sqlite_async package on the web.
16+
Use the console to open and interact with databases.
17+
18+
<pre>
19+
<code>
20+
const db = await open('test.db');
21+
const lock = await write_lock(db);
22+
release_lock(lock);
23+
</code>
24+
</pre>
25+
</main>
26+
27+
28+
</body>
29+
</html>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'dart:async';
2+
import 'dart:js_interop';
3+
import 'dart:js_interop_unsafe';
4+
5+
import 'package:sqlite_async/sqlite_async.dart';
6+
7+
void main() {
8+
globalContext['open'] = (String path) {
9+
return Future(() async {
10+
final db = SqliteDatabase(
11+
path: path,
12+
options: SqliteOptions(
13+
webSqliteOptions: WebSqliteOptions(
14+
wasmUri:
15+
'https://cdn.jsdelivr.net/npm/@powersync/dart-wasm-bundles@latest/dist/sqlite3.wasm',
16+
workerUri: 'worker.dart.js',
17+
),
18+
),
19+
);
20+
await db.initialize();
21+
return db.toJSBox;
22+
}).toJS;
23+
}.toJS;
24+
25+
globalContext['write_lock'] = (JSBoxedDartObject db) {
26+
final hasLock = Completer<void>();
27+
final completer = Completer<void>();
28+
29+
(db.toDart as SqliteDatabase).writeLock((_) async {
30+
print('has write lock!');
31+
hasLock.complete();
32+
await completer.future;
33+
});
34+
35+
return hasLock.future.then((_) => completer.toJSBox).toJS;
36+
}.toJS;
37+
38+
globalContext['release_lock'] = (JSBoxedDartObject db) {
39+
(db.toDart as Completer<void>).complete();
40+
}.toJS;
41+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import 'package:sqlite_async/sqlite3_web.dart';
2+
import 'package:sqlite_async/sqlite3_web_worker.dart';
3+
4+
void main() {
5+
WebSqlite.workerEntrypoint(controller: AsyncSqliteController());
6+
}

packages/sqlite_async/lib/src/web/worker/worker_utils.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,27 @@ class AsyncSqliteDatabase extends WorkerDatabase {
5656
// these requests for shared workers, so we can assume each database is only
5757
// opened once and we don't need web locks here.
5858
final mutex = ReadWriteMutex();
59+
final Map<ClientConnection, _ConnectionState> _state = {};
5960

6061
AsyncSqliteDatabase({required this.database});
6162

63+
_ConnectionState _findState(ClientConnection connection) {
64+
return _state.putIfAbsent(connection, _ConnectionState.new);
65+
}
66+
67+
void _markHoldsMutex(ClientConnection connection) {
68+
final state = _findState(connection);
69+
state.holdsMutex = true;
70+
if (!state.hasOnCloseListener) {
71+
state.hasOnCloseListener = true;
72+
connection.closed.then((_) {
73+
if (state.holdsMutex) {
74+
mutex.release();
75+
}
76+
});
77+
}
78+
}
79+
6280
@override
6381
Future<JSAny?> handleCustomRequest(
6482
ClientConnection connection, JSAny? request) async {
@@ -67,9 +85,12 @@ class AsyncSqliteDatabase extends WorkerDatabase {
6785
switch (message.kind) {
6886
case CustomDatabaseMessageKind.requestSharedLock:
6987
await mutex.acquireRead();
88+
_markHoldsMutex(connection);
7089
case CustomDatabaseMessageKind.requestExclusiveLock:
7190
await mutex.acquireWrite();
91+
_markHoldsMutex(connection);
7292
case CustomDatabaseMessageKind.releaseLock:
93+
_findState(connection).holdsMutex = false;
7394
mutex.release();
7495
case CustomDatabaseMessageKind.lockObtained:
7596
throw UnsupportedError('This is a response, not a request');
@@ -123,3 +144,8 @@ class AsyncSqliteDatabase extends WorkerDatabase {
123144
return resultSetMap;
124145
}
125146
}
147+
148+
final class _ConnectionState {
149+
bool hasOnCloseListener = false;
150+
bool holdsMutex = false;
151+
}

0 commit comments

Comments
 (0)