mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-31 13:28:18 +00:00
test(syncguard): protect catch-up handoff while finite pull settles
This commit is contained in:
1 parent
101fe958f5
commit
b552c2a455
2 files changed
+30
-2
No files matched your search
@@ -102,9 +102,14 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard
|
|||||||
guard.catchUpSucceeded();
|
guard.catchUpSucceeded();
|
||||||
guard.catchUpActivityChanged(false, readPendingCount(), Date.now());
|
guard.catchUpActivityChanged(false, readPendingCount(), Date.now());
|
||||||
} else if (guard.presentation.phase !== "idle" && terminalPhaseTimer === undefined) {
|
} else if (guard.presentation.phase !== "idle" && terminalPhaseTimer === undefined) {
|
||||||
|
// The one-shot PouchDB progress observer can report 'idle' before the
|
||||||
|
// enclosing finite CouchDB pull has finished releasing its resources.
|
||||||
|
// This counter is not used to identify the provider; CouchDB phase owns that.
|
||||||
|
if (phase === "idle" && services.replicator.finiteReplicationActivityCount.value > 0) return;
|
||||||
// PouchDB disposes its per-attempt progress observer with phase 'idle' just
|
// PouchDB disposes its per-attempt progress observer with phase 'idle' just
|
||||||
// before Commonlib publishes 'continuous'. Defer the terminal observation by
|
// before Commonlib publishes 'continuous'. When the enclosing finite
|
||||||
// one event-loop turn so that this internal hand-off never looks like a failure.
|
// transfer settles, defer the terminal observation by one event-loop
|
||||||
|
// turn so that this internal hand-off never looks like a failure.
|
||||||
terminalPhaseTimer = compatGlobal.setTimeout(() => {
|
terminalPhaseTimer = compatGlobal.setTimeout(() => {
|
||||||
terminalPhaseTimer = undefined;
|
terminalPhaseTimer = undefined;
|
||||||
const current = services.replicator.replicationStatics.value.couchDB?.phase;
|
const current = services.replicator.replicationStatics.value.couchDB?.phase;
|
||||||
@@ -119,6 +124,9 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard
|
|||||||
else guard.updateThreshold(services.setting.currentSettings().syncGuardMinimumFiles);
|
else guard.updateThreshold(services.setting.currentSettings().syncGuardMinimumFiles);
|
||||||
};
|
};
|
||||||
const onPendingChanged = () => checkForUnlock();
|
const onPendingChanged = () => checkForUnlock();
|
||||||
|
const onFiniteChanged = () => {
|
||||||
|
if (services.replicator.finiteReplicationActivityCount.value === 0) onReplicationChanged();
|
||||||
|
};
|
||||||
const removeReflectionFailureHandler = services.context.events.onEvent(EVENT_VAULT_REFLECTION_FAILED, () => {
|
const removeReflectionFailureHandler = services.context.events.onEvent(EVENT_VAULT_REFLECTION_FAILED, () => {
|
||||||
guard.reflectionFailed();
|
guard.reflectionFailed();
|
||||||
});
|
});
|
||||||
@@ -127,6 +135,7 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard
|
|||||||
});
|
});
|
||||||
|
|
||||||
const replication = services.replicator.replicationStatics;
|
const replication = services.replicator.replicationStatics;
|
||||||
|
const finite = services.replicator.finiteReplicationActivityCount;
|
||||||
const pendingSources = [
|
const pendingSources = [
|
||||||
services.replication.replicationResultCount,
|
services.replication.replicationResultCount,
|
||||||
services.replication.databaseQueueCount,
|
services.replication.databaseQueueCount,
|
||||||
@@ -139,6 +148,7 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard
|
|||||||
chunkFetchCounts,
|
chunkFetchCounts,
|
||||||
];
|
];
|
||||||
replication.onChanged(onReplicationChanged);
|
replication.onChanged(onReplicationChanged);
|
||||||
|
finite.onChanged(onFiniteChanged);
|
||||||
for (const source of pendingSources) source.onChanged(onPendingChanged);
|
for (const source of pendingSources) source.onChanged(onPendingChanged);
|
||||||
|
|
||||||
// Register before the ordinary replication scheduling handler so the first finite pull
|
// Register before the ordinary replication scheduling handler so the first finite pull
|
||||||
@@ -164,6 +174,7 @@ export function useSyncGuardFeature(host: LiveSyncCore, createOverlay: SyncGuard
|
|||||||
stopTimer();
|
stopTimer();
|
||||||
stopTerminalPhaseTimer();
|
stopTerminalPhaseTimer();
|
||||||
replication.offChanged(onReplicationChanged);
|
replication.offChanged(onReplicationChanged);
|
||||||
|
finite.offChanged(onFiniteChanged);
|
||||||
for (const source of pendingSources) source.offChanged(onPendingChanged);
|
for (const source of pendingSources) source.offChanged(onPendingChanged);
|
||||||
overlay.dispose();
|
overlay.dispose();
|
||||||
removeSettingSavedHandler();
|
removeSettingSavedHandler();
|
||||||
|
|||||||
@@ -159,6 +159,23 @@ describe("useSyncGuardFeature", () => {
|
|||||||
await h.onUnload[0]();
|
await h.onUnload[0]();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("waits for the finite pull to settle when progress temporarily reports idle", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const h = setup({ minimumFiles: 1 });
|
||||||
|
await h.onResumed[0]();
|
||||||
|
h.setCouchDbPhase("catch-up");
|
||||||
|
h.finiteReplicationActivityCount.value = 1;
|
||||||
|
await h.parseHandlers[0]([receivedNote("first")]);
|
||||||
|
h.setCouchDbPhase("idle");
|
||||||
|
await vi.advanceTimersByTimeAsync(5_000); // Resource disposal may be asynchronous.
|
||||||
|
expect(h.updates[h.updates.length - 1]?.phase).toBe("transferring");
|
||||||
|
h.finiteReplicationActivityCount.value = 0;
|
||||||
|
h.setCouchDbPhase("continuous");
|
||||||
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
|
expect(h.updates[h.updates.length - 1]?.phase).toBe("applying");
|
||||||
|
await h.onUnload[0]();
|
||||||
|
});
|
||||||
|
|
||||||
it("ignores large P2P finite transfers when CouchDB catch-up is not running", async () => {
|
it("ignores large P2P finite transfers when CouchDB catch-up is not running", async () => {
|
||||||
const h = setup({ minimumFiles: 2 });
|
const h = setup({ minimumFiles: 2 });
|
||||||
await h.onResumed[0]();
|
await h.onResumed[0]();
|
||||||
|
|||||||
Reference in new issue
Block a user