-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathStreamingSyncRetryTests.cs
More file actions
106 lines (91 loc) · 3.4 KB
/
Copy pathStreamingSyncRetryTests.cs
File metadata and controls
106 lines (91 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
98
99
100
101
102
103
104
105
106
namespace PowerSync.Common.Tests.Client.Sync;
using System.Collections.Concurrent;
using PowerSync.Common.Client;
using PowerSync.Common.Client.Connection;
using PowerSync.Common.Client.Sync.Stream;
using PowerSync.Common.Tests.Utils;
using PowerSync.Common.Tests.Utils.Sync;
/// <summary>
/// dotnet test -v n --framework net8.0 --filter "StreamingSyncRetryTests"
/// </summary>
public class StreamingSyncRetryTests
{
[Fact(Timeout = 15000)]
public async Task RetryLoop_AppliesDelayBetweenFailedAttempts()
{
const int retryDelayMs = 200;
const double tolerance = 0.75;
var attemptTimes = new ConcurrentQueue<DateTime>();
var attemptSignal = new SemaphoreSlim(0);
var dbFilename = $"sync-retry-{Guid.NewGuid():N}.db";
var throwing = new ThrowingRemote(new TestConnector(), attemptTimes, attemptSignal);
var db = new PowerSyncDatabase(new PowerSyncDatabaseOptions
{
Database = new SQLOpenOptions { DbFilename = dbFilename },
Schema = TestSchemaTodoList.AppSchema,
RemoteFactory = _ => throwing
});
try
{
await db.Init();
// Fire-and-forget: Connect() awaits Connected=true, which never fires
// because every iteration throws. The retry loop runs in the background.
_ = db.Connect(
new TestConnector(),
new PowerSyncConnectionOptions { RetryDelayMs = retryDelayMs }
);
for (int i = 0; i < 4; i++)
{
Assert.True(
await attemptSignal.WaitAsync(TimeSpan.FromSeconds(5)),
$"Did not observe attempt #{i + 1} within timeout — retry loop is not running"
);
}
var timestamps = attemptTimes.ToArray();
Assert.True(timestamps.Length >= 4);
for (int i = 1; i < timestamps.Length; i++)
{
var deltaMs = (timestamps[i] - timestamps[i - 1]).TotalMilliseconds;
Assert.True(
deltaMs >= retryDelayMs * tolerance,
$"Retry gap #{i} was {deltaMs:F0}ms, expected >= {retryDelayMs * tolerance:F0}ms (RetryDelayMs={retryDelayMs})"
);
}
}
finally
{
await db.Disconnect();
await db.Close();
DatabaseUtils.CleanDb(dbFilename);
}
}
}
internal sealed class ThrowingRemote : Remote
{
private readonly ConcurrentQueue<DateTime> timestamps;
private readonly SemaphoreSlim signal;
public ThrowingRemote(
IPowerSyncBackendConnector connector,
ConcurrentQueue<DateTime> timestamps,
SemaphoreSlim signal
) : base(connector)
{
this.timestamps = timestamps;
this.signal = signal;
}
public override Task<System.IO.Stream> PostStreamRaw(SyncStreamOptions options)
{
timestamps.Enqueue(DateTime.UtcNow);
signal.Release();
throw new HttpRequestException(
"HTTP InternalServerError: simulated [PSYNC_S2305] from ThrowingRemote"
);
}
public override Task<T> Get<T>(string path, Dictionary<string, string>? headers = null)
{
var response = new StreamingSyncImplementation.ApiResponse(
new StreamingSyncImplementation.ResponseData("1")
);
return Task.FromResult((T)(object)response);
}
}