Skip to content

Commit 8cc1337

Browse files
ChriztiaanChristiaan Landman
andauthored
[Fix] - Possible race condition/handling uncaught exception in watch() (#120)
Co-authored-by: Christiaan Landman <[email protected]>
1 parent a0119f0 commit 8cc1337

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

.changeset/fresh-fishes-own.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@journeyapps/powersync-sdk-common': patch
3+
---
4+
5+
Resolving tables for watch() before handling any results, eliminating a potential race condition between initial result and changes. Also handling a potential uncaught exception.

packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
476476
}
477477

478478
/**
479-
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
480-
* and optionally return results.
479+
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
480+
* and optionally return results.
481481
* This is faster than executing separately with each parameter set.
482482
*/
483483
async executeBatch(sql: string, parameters?: any[][]) {
@@ -637,13 +637,24 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
637637

638638
(async () => {
639639
try {
640-
// Fetch initial data
641-
onResult(await this.executeReadOnly(sql, parameters));
642-
643640
const resolvedTables = await this.resolveTables(sql, parameters, options);
644641

642+
// Fetch initial data
643+
const result = await this.executeReadOnly(sql, parameters);
644+
onResult(result);
645+
645646
this.onChangeWithCallback(
646-
{ onChange: async () => onResult(await this.executeReadOnly(sql, parameters)), onError },
647+
{
648+
onChange: async () => {
649+
try {
650+
const result = await this.executeReadOnly(sql, parameters);
651+
onResult(result);
652+
} catch (error) {
653+
onError?.(error);
654+
}
655+
},
656+
onError
657+
},
647658
{
648659
...(options ?? {}),
649660
tables: resolvedTables
@@ -663,11 +674,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
663674
watchWithAsyncGenerator(sql: string, parameters?: any[], options?: SQLWatchOptions): AsyncIterable<QueryResult> {
664675
return new EventIterator<QueryResult>((eventOptions) => {
665676
(async () => {
677+
const resolvedTables = await this.resolveTables(sql, parameters, options);
678+
666679
// Fetch initial data
667680
eventOptions.push(await this.executeReadOnly(sql, parameters));
668681

669-
const resolvedTables = await this.resolveTables(sql, parameters, options);
670-
671682
for await (const event of this.onChangeWithAsyncGenerator({
672683
...(options ?? {}),
673684
tables: resolvedTables

packages/powersync-sdk-web/tests/watch.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ describe('Watch Tests', () => {
195195
}
196196
})();
197197

198+
// Ensures insert doesn't form part of initial result
199+
await new Promise<void>((resolve) => setTimeout(resolve, throttleDuration));
200+
198201
// Create the inserts as fast as possible
199202
await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]);
200203

@@ -238,7 +241,9 @@ describe('Watch Tests', () => {
238241
}
239242
);
240243

241-
// Create the inserts as fast as possible
244+
// Ensures insert doesn't form part of initial result
245+
await new Promise<void>((resolve) => setTimeout(resolve, throttleDuration));
246+
242247
await powersync.execute('INSERT INTO assets(id, make, customer_id) VALUES (uuid(), ?, ?)', ['test', uuid()]);
243248

244249
await new Promise<void>((resolve) => setTimeout(resolve, throttleDuration * 2));

0 commit comments

Comments
 (0)