From b7476686108bdcee2aebeb5d8bc6f0adb44df6c3 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Thu, 26 Feb 2026 09:36:36 +0200 Subject: [PATCH 1/2] Update to core extension 0.4.11 --- PowerSync/PowerSync.Common/CHANGELOG.md | 1 + Tools/Setup/Setup.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/PowerSync/PowerSync.Common/CHANGELOG.md b/PowerSync/PowerSync.Common/CHANGELOG.md index b9cc091..be75d9b 100644 --- a/PowerSync/PowerSync.Common/CHANGELOG.md +++ b/PowerSync/PowerSync.Common/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.0.11-alpha.1 +- Updated to the latest version (0.4.11) of the core extension. - Removed the `RunListener` and `RunListenerAsync` APIs from `IEventStream`. Users are encouraged to use the `Listen` or `ListenAsync` APIs instead (`RunListener` itself was implemented using the `Listen` API). - Changed the `PowerSyncDatabase.Watch` syntax to return an IAsyncEnumerable instead of accepting a callback. This allows users to handle database changes when they are ready instead of us eagerly running the callback as soon as a change is detected. diff --git a/Tools/Setup/Setup.cs b/Tools/Setup/Setup.cs index 17c5426..0ceb5d4 100644 --- a/Tools/Setup/Setup.cs +++ b/Tools/Setup/Setup.cs @@ -10,7 +10,7 @@ /// public class PowerSyncSetup { - private const string VERSION = "0.4.10"; + private const string VERSION = "0.4.11"; private const string GITHUB_BASE_URL = $"https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v{VERSION}"; private const string MAVEN_BASE_URL = $"https://repo1.maven.org/maven2/com/powersync/powersync-sqlite-core/{VERSION}"; @@ -239,4 +239,4 @@ static async Task Main(string[] args) var setup = new PowerSyncSetup(); await setup.RunSetup(); } -} \ No newline at end of file +} From c7125deb751e20edf1d715bd824ae4fb04277895 Mon Sep 17 00:00:00 2001 From: LucDeCaf Date: Thu, 26 Feb 2026 13:49:10 +0200 Subject: [PATCH 2/2] Fix race conditions --- .../Client/PowerSyncDatabase.cs | 27 ++++++++++--------- .../PowerSync.Common.Tests/GlobalUsing.cs | 1 + .../Utils/FactAttribute.cs | 10 +++++++ .../Utils/Sync/MockSyncService.cs | 3 ++- 4 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 Tests/PowerSync/PowerSync.Common.Tests/GlobalUsing.cs create mode 100644 Tests/PowerSync/PowerSync.Common.Tests/Utils/FactAttribute.cs diff --git a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs index 176089d..52e9eda 100644 --- a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs +++ b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs @@ -204,10 +204,11 @@ public PowerSyncDatabase(PowerSyncDatabaseOptions options) Logger = Logger }); - var syncStreamStatusCts = new CancellationTokenSource(); - var _ = Task.Run(() => + var syncStreamStatusCts = CancellationTokenSource.CreateLinkedTokenSource(masterCts.Token); + var syncStreamStatusListener = syncStreamImplementation.ListenAsync(syncStreamStatusCts.Token); + var _ = Task.Run(async () => { - foreach (var update in syncStreamImplementation.Listen(syncStreamStatusCts.Token)) + await foreach (var update in syncStreamStatusListener) { if (update.StatusChanged != null) { @@ -280,36 +281,36 @@ public async Task WaitForFirstSync(PrioritySyncRequest? request = null) /// Optional cancellation token to abort the wait. public async Task WaitForStatus(Func predicate, CancellationToken? cancellationToken = null) { + var cts = cancellationToken == null + ? CancellationTokenSource.CreateLinkedTokenSource(masterCts.Token) + : CancellationTokenSource.CreateLinkedTokenSource(masterCts.Token, cancellationToken.Value); + + var statusListener = ListenAsync(cts.Token); + if (predicate(CurrentStatus)) { + cts.Cancel(); return; } var tcs = new TaskCompletionSource(); - var cts = CancellationTokenSource.CreateLinkedTokenSource(masterCts.Token); _ = Task.Run(async () => { try { - await foreach (var update in ListenAsync(cts.Token)) + await foreach (var update in statusListener) { - if (update.StatusChanged != null && predicate(update.StatusChanged)) + if ((update.StatusChanged != null) && predicate(update.StatusChanged)) { - cts.Cancel(); tcs.TrySetResult(true); + cts.Cancel(); } } } catch (OperationCanceledException) { } }); - cancellationToken?.Register(() => - { - cts.Cancel(); - tcs.TrySetCanceled(); - }); - await tcs.Task; } diff --git a/Tests/PowerSync/PowerSync.Common.Tests/GlobalUsing.cs b/Tests/PowerSync/PowerSync.Common.Tests/GlobalUsing.cs new file mode 100644 index 0000000..cca7737 --- /dev/null +++ b/Tests/PowerSync/PowerSync.Common.Tests/GlobalUsing.cs @@ -0,0 +1 @@ +global using FactAttribute = PowerSync.Common.Tests.Utils.FactAttribute; diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Utils/FactAttribute.cs b/Tests/PowerSync/PowerSync.Common.Tests/Utils/FactAttribute.cs new file mode 100644 index 0000000..53c67cf --- /dev/null +++ b/Tests/PowerSync/PowerSync.Common.Tests/Utils/FactAttribute.cs @@ -0,0 +1,10 @@ +namespace PowerSync.Common.Tests.Utils; + +[AttributeUsage(AttributeTargets.Method)] +public class FactAttribute : Xunit.FactAttribute +{ + public FactAttribute() + { + Timeout = 5000; + } +} diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Utils/Sync/MockSyncService.cs b/Tests/PowerSync/PowerSync.Common.Tests/Utils/Sync/MockSyncService.cs index 3a93f36..e9d46c5 100644 --- a/Tests/PowerSync/PowerSync.Common.Tests/Utils/Sync/MockSyncService.cs +++ b/Tests/PowerSync/PowerSync.Common.Tests/Utils/Sync/MockSyncService.cs @@ -165,11 +165,12 @@ public override async Task PostStreamRaw(SyncStreamOptions options) var writer = pipe.Writer; var cts = CancellationTokenSource.CreateLinkedTokenSource(options.CancellationToken); + var listener = syncService.ListenAsync(cts.Token); _ = Task.Run(async () => { try { - await foreach (var line in syncService.ListenAsync(cts.Token)) + await foreach (var line in listener) { var bytes = Encoding.UTF8.GetBytes(line + "\n"); await writer.WriteAsync(bytes);