cojson 0.19.22 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +66 -0
- package/dist/PeerState.d.ts +6 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +18 -3
- package/dist/PeerState.js.map +1 -1
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +70 -5
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +302 -31
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +4 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +3 -0
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +2 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -12
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +5 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +4 -3
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/queue/LinkedList.d.ts +9 -3
- package/dist/queue/LinkedList.d.ts.map +1 -1
- package/dist/queue/LinkedList.js +30 -1
- package/dist/queue/LinkedList.js.map +1 -1
- package/dist/queue/OutgoingLoadQueue.d.ts +95 -0
- package/dist/queue/OutgoingLoadQueue.d.ts.map +1 -0
- package/dist/queue/OutgoingLoadQueue.js +240 -0
- package/dist/queue/OutgoingLoadQueue.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +66 -43
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/LinkedList.test.js +90 -0
- package/dist/tests/LinkedList.test.js.map +1 -1
- package/dist/tests/OutgoingLoadQueue.test.d.ts +2 -0
- package/dist/tests/OutgoingLoadQueue.test.d.ts.map +1 -0
- package/dist/tests/OutgoingLoadQueue.test.js +814 -0
- package/dist/tests/OutgoingLoadQueue.test.js.map +1 -0
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.concurrentLoad.test.d.ts +2 -0
- package/dist/tests/sync.concurrentLoad.test.d.ts.map +1 -0
- package/dist/tests/sync.concurrentLoad.test.js +481 -0
- package/dist/tests/sync.concurrentLoad.test.js.map +1 -0
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +4 -3
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +17 -1
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +7 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +19 -4
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/PeerState.ts +26 -3
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +415 -27
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +9 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +4 -13
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +4 -2
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/queue/LinkedList.ts +34 -4
- package/src/queue/OutgoingLoadQueue.ts +307 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +78 -46
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/LinkedList.test.ts +111 -0
- package/src/tests/OutgoingLoadQueue.test.ts +1129 -0
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.concurrentLoad.test.ts +650 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +6 -3
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +31 -2
- package/src/tests/testUtils.ts +31 -10
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { CO_VALUE_LOADING_CONFIG, setMaxInFlightLoadsPerPeer, } from "../config.js";
|
|
3
|
+
import { OutgoingLoadQueue } from "../queue/OutgoingLoadQueue.js";
|
|
4
|
+
import { createTestNode } from "./testUtils.js";
|
|
5
|
+
const TEST_PEER_ID = "test-peer";
|
|
6
|
+
// Store original config values
|
|
7
|
+
let originalMaxInFlightLoads;
|
|
8
|
+
let originalTimeout;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
originalMaxInFlightLoads =
|
|
11
|
+
CO_VALUE_LOADING_CONFIG.MAX_IN_FLIGHT_LOADS_PER_PEER;
|
|
12
|
+
originalTimeout = CO_VALUE_LOADING_CONFIG.TIMEOUT;
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
// Restore original config
|
|
16
|
+
setMaxInFlightLoadsPerPeer(originalMaxInFlightLoads);
|
|
17
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = originalTimeout;
|
|
18
|
+
vi.useRealTimers();
|
|
19
|
+
});
|
|
20
|
+
describe("OutgoingLoadQueue", () => {
|
|
21
|
+
describe("basic enqueue behavior", () => {
|
|
22
|
+
test("should call sendCallback immediately when queue has capacity", () => {
|
|
23
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
24
|
+
const node = createTestNode();
|
|
25
|
+
const group = node.createGroup();
|
|
26
|
+
const map = group.createMap();
|
|
27
|
+
let callbackCalled = false;
|
|
28
|
+
queue.enqueue(map.core, () => {
|
|
29
|
+
callbackCalled = true;
|
|
30
|
+
});
|
|
31
|
+
expect(callbackCalled).toBe(true);
|
|
32
|
+
expect(queue.inFlightCount).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
test("should track sent request in inFlightLoads", () => {
|
|
35
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
36
|
+
const node = createTestNode();
|
|
37
|
+
const group = node.createGroup();
|
|
38
|
+
const map = group.createMap();
|
|
39
|
+
queue.enqueue(map.core, () => { });
|
|
40
|
+
expect(queue.inFlightCount).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("FIFO ordering", () => {
|
|
44
|
+
test("should maintain FIFO order for pending requests", () => {
|
|
45
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
46
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
47
|
+
const node = createTestNode();
|
|
48
|
+
const group = node.createGroup();
|
|
49
|
+
// Block the queue first
|
|
50
|
+
const blockerMap = group.createMap();
|
|
51
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
52
|
+
// Enqueue multiple CoValues
|
|
53
|
+
const map1 = group.createMap();
|
|
54
|
+
const map2 = group.createMap();
|
|
55
|
+
const map3 = group.createMap();
|
|
56
|
+
const order = [];
|
|
57
|
+
queue.enqueue(map1.core, () => order.push("map1"));
|
|
58
|
+
queue.enqueue(map2.core, () => order.push("map2"));
|
|
59
|
+
queue.enqueue(map3.core, () => order.push("map3"));
|
|
60
|
+
// Complete requests one by one
|
|
61
|
+
queue.trackComplete(blockerMap.core);
|
|
62
|
+
queue.trackComplete(map1.core);
|
|
63
|
+
queue.trackComplete(map2.core);
|
|
64
|
+
expect(order).toEqual(["map1", "map2", "map3"]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("throttling", () => {
|
|
68
|
+
test("should queue requests when at capacity limit", () => {
|
|
69
|
+
setMaxInFlightLoadsPerPeer(2);
|
|
70
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
71
|
+
const node = createTestNode();
|
|
72
|
+
const group = node.createGroup();
|
|
73
|
+
const map1 = group.createMap();
|
|
74
|
+
const map2 = group.createMap();
|
|
75
|
+
const map3 = group.createMap();
|
|
76
|
+
let callback3Called = false;
|
|
77
|
+
queue.enqueue(map1.core, () => { });
|
|
78
|
+
queue.enqueue(map2.core, () => { });
|
|
79
|
+
queue.enqueue(map3.core, () => {
|
|
80
|
+
callback3Called = true;
|
|
81
|
+
});
|
|
82
|
+
// First two should be in flight
|
|
83
|
+
expect(queue.inFlightCount).toBe(2);
|
|
84
|
+
// Third should be pending
|
|
85
|
+
expect(queue.pendingCount).toBe(1);
|
|
86
|
+
expect(callback3Called).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
test("should process queued requests when a slot becomes available", () => {
|
|
89
|
+
setMaxInFlightLoadsPerPeer(2);
|
|
90
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
91
|
+
const node = createTestNode();
|
|
92
|
+
const group = node.createGroup();
|
|
93
|
+
const map1 = group.createMap();
|
|
94
|
+
const map2 = group.createMap();
|
|
95
|
+
const map3 = group.createMap();
|
|
96
|
+
let callback3Called = false;
|
|
97
|
+
queue.enqueue(map1.core, () => { });
|
|
98
|
+
queue.enqueue(map2.core, () => { });
|
|
99
|
+
queue.enqueue(map3.core, () => {
|
|
100
|
+
callback3Called = true;
|
|
101
|
+
});
|
|
102
|
+
expect(callback3Called).toBe(false);
|
|
103
|
+
// Complete one request
|
|
104
|
+
queue.trackComplete(map1.core);
|
|
105
|
+
// Third should now be processed
|
|
106
|
+
expect(callback3Called).toBe(true);
|
|
107
|
+
expect(queue.inFlightCount).toBe(2);
|
|
108
|
+
expect(queue.pendingCount).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("request deduplication", () => {
|
|
112
|
+
test("should skip duplicate enqueue while pending", () => {
|
|
113
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
114
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
115
|
+
const node = createTestNode();
|
|
116
|
+
const group = node.createGroup();
|
|
117
|
+
const blockerMap = group.createMap();
|
|
118
|
+
const targetMap = group.createMap();
|
|
119
|
+
let targetCallbackCount = 0;
|
|
120
|
+
let duplicateCallbackCount = 0;
|
|
121
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
122
|
+
queue.enqueue(targetMap.core, () => {
|
|
123
|
+
targetCallbackCount += 1;
|
|
124
|
+
});
|
|
125
|
+
queue.enqueue(targetMap.core, () => {
|
|
126
|
+
duplicateCallbackCount += 1;
|
|
127
|
+
});
|
|
128
|
+
expect(queue.pendingCount).toBe(1);
|
|
129
|
+
expect(targetCallbackCount).toBe(0);
|
|
130
|
+
expect(duplicateCallbackCount).toBe(0);
|
|
131
|
+
queue.trackComplete(blockerMap.core);
|
|
132
|
+
expect(targetCallbackCount).toBe(1);
|
|
133
|
+
expect(duplicateCallbackCount).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
test("should skip duplicate enqueue while in-flight", () => {
|
|
136
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
137
|
+
const node = createTestNode();
|
|
138
|
+
const group = node.createGroup();
|
|
139
|
+
const map = group.createMap();
|
|
140
|
+
let duplicateCallbackCount = 0;
|
|
141
|
+
queue.enqueue(map.core, () => { });
|
|
142
|
+
queue.enqueue(map.core, () => {
|
|
143
|
+
duplicateCallbackCount += 1;
|
|
144
|
+
});
|
|
145
|
+
expect(queue.inFlightCount).toBe(1);
|
|
146
|
+
expect(duplicateCallbackCount).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
test("should skip duplicate enqueue for same CoValue ID", () => {
|
|
149
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
150
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
151
|
+
const node = createTestNode();
|
|
152
|
+
const otherNode = createTestNode();
|
|
153
|
+
const group = node.createGroup();
|
|
154
|
+
const availableMap = group.createMap();
|
|
155
|
+
const sameIdCoValue = otherNode.getCoValue(availableMap.id);
|
|
156
|
+
queue.enqueue(availableMap.core, () => { });
|
|
157
|
+
queue.trackComplete(availableMap.core);
|
|
158
|
+
// Block capacity so the next enqueue stays pending
|
|
159
|
+
const blockerMap = group.createMap();
|
|
160
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
161
|
+
let duplicateCallbackCount = 0;
|
|
162
|
+
queue.enqueue(availableMap.core, () => { });
|
|
163
|
+
queue.enqueue(sameIdCoValue, () => {
|
|
164
|
+
duplicateCallbackCount += 1;
|
|
165
|
+
});
|
|
166
|
+
expect(queue.pendingCount).toBe(1);
|
|
167
|
+
expect(duplicateCallbackCount).toBe(0);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe("timeout behavior", () => {
|
|
171
|
+
test("should mark CoValue as not found in peer after timeout", async () => {
|
|
172
|
+
vi.useFakeTimers();
|
|
173
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
174
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
175
|
+
const node = createTestNode();
|
|
176
|
+
// Get an unavailable CoValue
|
|
177
|
+
const coValue = node.getCoValue("co_zTestTimeoutCoValue0001");
|
|
178
|
+
queue.enqueue(coValue, () => { });
|
|
179
|
+
expect(queue.inFlightCount).toBe(1);
|
|
180
|
+
// Advance time past the timeout
|
|
181
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
182
|
+
// Should be removed from in-flight
|
|
183
|
+
expect(queue.inFlightCount).toBe(0);
|
|
184
|
+
// Should be marked as not found
|
|
185
|
+
expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
186
|
+
});
|
|
187
|
+
test("should free the queue slot and process pending requests on timeout", async () => {
|
|
188
|
+
vi.useFakeTimers();
|
|
189
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
190
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
191
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
192
|
+
const node = createTestNode();
|
|
193
|
+
const group = node.createGroup();
|
|
194
|
+
const coValue1 = node.getCoValue("co_zTestTimeoutFree00000001");
|
|
195
|
+
const map2 = group.createMap();
|
|
196
|
+
let callback2Called = false;
|
|
197
|
+
queue.enqueue(coValue1, () => { });
|
|
198
|
+
queue.enqueue(map2.core, () => {
|
|
199
|
+
callback2Called = true;
|
|
200
|
+
});
|
|
201
|
+
expect(queue.inFlightCount).toBe(1);
|
|
202
|
+
expect(callback2Called).toBe(false);
|
|
203
|
+
// Advance time past the timeout
|
|
204
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
205
|
+
// First should have timed out, second should be processed
|
|
206
|
+
expect(callback2Called).toBe(true);
|
|
207
|
+
expect(queue.inFlightCount).toBe(1);
|
|
208
|
+
});
|
|
209
|
+
test("should timeout each in-flight load independently", async () => {
|
|
210
|
+
vi.useFakeTimers();
|
|
211
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
212
|
+
setMaxInFlightLoadsPerPeer(3);
|
|
213
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
214
|
+
const node = createTestNode();
|
|
215
|
+
const coValue1 = node.getCoValue("co_zTestSingleTimer000001");
|
|
216
|
+
const coValue2 = node.getCoValue("co_zTestSingleTimer000002");
|
|
217
|
+
const coValue3 = node.getCoValue("co_zTestSingleTimer000003");
|
|
218
|
+
queue.enqueue(coValue1, () => { });
|
|
219
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
220
|
+
queue.enqueue(coValue2, () => { });
|
|
221
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
222
|
+
queue.enqueue(coValue3, () => { });
|
|
223
|
+
expect(queue.inFlightCount).toBe(3);
|
|
224
|
+
// Advance time past the timeout
|
|
225
|
+
await vi.advanceTimersByTimeAsync(801);
|
|
226
|
+
// All three should have timed out
|
|
227
|
+
expect(queue.inFlightCount).toBe(2);
|
|
228
|
+
await vi.advanceTimersByTimeAsync(101);
|
|
229
|
+
expect(coValue1.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
230
|
+
expect(queue.inFlightCount).toBe(1);
|
|
231
|
+
await vi.advanceTimersByTimeAsync(101);
|
|
232
|
+
expect(coValue2.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
233
|
+
expect(queue.inFlightCount).toBe(0);
|
|
234
|
+
await vi.advanceTimersByTimeAsync(101);
|
|
235
|
+
expect(coValue3.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
236
|
+
expect(queue.inFlightCount).toBe(0);
|
|
237
|
+
});
|
|
238
|
+
test("should allow re-enqueue after timeout", async () => {
|
|
239
|
+
vi.useFakeTimers();
|
|
240
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
241
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
242
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
243
|
+
const node = createTestNode();
|
|
244
|
+
const coValue = node.getCoValue("co_zTestTimeoutReenqueue0001");
|
|
245
|
+
let secondCallbackCount = 0;
|
|
246
|
+
queue.enqueue(coValue, () => { });
|
|
247
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
248
|
+
expect(queue.inFlightCount).toBe(0);
|
|
249
|
+
expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
250
|
+
queue.enqueue(coValue, () => {
|
|
251
|
+
secondCallbackCount += 1;
|
|
252
|
+
});
|
|
253
|
+
expect(queue.inFlightCount).toBe(1);
|
|
254
|
+
expect(secondCallbackCount).toBe(1);
|
|
255
|
+
});
|
|
256
|
+
test("should warn but not mark as unavailable when streaming CoValue times out", async () => {
|
|
257
|
+
vi.useFakeTimers();
|
|
258
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
259
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
260
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
261
|
+
const node = createTestNode();
|
|
262
|
+
const group = node.createGroup();
|
|
263
|
+
const map = group.createMap();
|
|
264
|
+
map.set("key", "value");
|
|
265
|
+
// Mock isStreaming to return true (CoValue is available but still streaming)
|
|
266
|
+
vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
|
|
267
|
+
queue.enqueue(map.core, () => { });
|
|
268
|
+
expect(queue.inFlightCount).toBe(1);
|
|
269
|
+
// Advance time past the timeout
|
|
270
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
271
|
+
// Should be removed from in-flight (timeout clears the slot)
|
|
272
|
+
expect(queue.inFlightCount).toBe(0);
|
|
273
|
+
// But should NOT be marked as unavailable since it's available (just streaming)
|
|
274
|
+
expect(map.core.isAvailable()).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
describe("trackComplete", () => {
|
|
278
|
+
test("should remove from inFlightLoads and allow next request to process", () => {
|
|
279
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
280
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
281
|
+
const node = createTestNode();
|
|
282
|
+
const group = node.createGroup();
|
|
283
|
+
const map1 = group.createMap();
|
|
284
|
+
const map2 = group.createMap();
|
|
285
|
+
let callback2Called = false;
|
|
286
|
+
queue.enqueue(map1.core, () => { });
|
|
287
|
+
queue.enqueue(map2.core, () => {
|
|
288
|
+
callback2Called = true;
|
|
289
|
+
});
|
|
290
|
+
expect(queue.inFlightCount).toBe(1);
|
|
291
|
+
expect(callback2Called).toBe(false);
|
|
292
|
+
queue.trackComplete(map1.core);
|
|
293
|
+
expect(callback2Called).toBe(true);
|
|
294
|
+
expect(queue.inFlightCount).toBe(1);
|
|
295
|
+
});
|
|
296
|
+
test("should be a no-op for unknown CoValues", () => {
|
|
297
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
298
|
+
const node = createTestNode();
|
|
299
|
+
const group = node.createGroup();
|
|
300
|
+
const map1 = group.createMap();
|
|
301
|
+
const unknownCoValue = node.getCoValue("co_zTestUnknownCoValue001");
|
|
302
|
+
queue.enqueue(map1.core, () => { });
|
|
303
|
+
expect(queue.inFlightCount).toBe(1);
|
|
304
|
+
// trackComplete on unknown CoValue should be a no-op
|
|
305
|
+
queue.trackComplete(unknownCoValue);
|
|
306
|
+
expect(queue.inFlightCount).toBe(1);
|
|
307
|
+
});
|
|
308
|
+
test("should allow re-enqueue after completion", () => {
|
|
309
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
310
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
311
|
+
const node = createTestNode();
|
|
312
|
+
const group = node.createGroup();
|
|
313
|
+
const map = group.createMap();
|
|
314
|
+
let firstCallbackCount = 0;
|
|
315
|
+
let secondCallbackCount = 0;
|
|
316
|
+
queue.enqueue(map.core, () => {
|
|
317
|
+
firstCallbackCount += 1;
|
|
318
|
+
});
|
|
319
|
+
expect(queue.inFlightCount).toBe(1);
|
|
320
|
+
expect(firstCallbackCount).toBe(1);
|
|
321
|
+
queue.trackComplete(map.core);
|
|
322
|
+
queue.enqueue(map.core, () => {
|
|
323
|
+
secondCallbackCount += 1;
|
|
324
|
+
});
|
|
325
|
+
expect(queue.inFlightCount).toBe(1);
|
|
326
|
+
expect(secondCallbackCount).toBe(1);
|
|
327
|
+
});
|
|
328
|
+
test("should not complete if CoValue is streaming", () => {
|
|
329
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
330
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
331
|
+
const node = createTestNode();
|
|
332
|
+
const group = node.createGroup();
|
|
333
|
+
const map1 = group.createMap();
|
|
334
|
+
const map2 = group.createMap();
|
|
335
|
+
// Mock isStreaming to return true
|
|
336
|
+
vi.spyOn(map1.core, "isStreaming").mockReturnValue(true);
|
|
337
|
+
let callback2Called = false;
|
|
338
|
+
queue.enqueue(map1.core, () => { });
|
|
339
|
+
queue.enqueue(map2.core, () => {
|
|
340
|
+
callback2Called = true;
|
|
341
|
+
});
|
|
342
|
+
expect(queue.inFlightCount).toBe(1);
|
|
343
|
+
expect(callback2Called).toBe(false);
|
|
344
|
+
// trackComplete should not complete because map1 is streaming
|
|
345
|
+
queue.trackComplete(map1.core);
|
|
346
|
+
// Should still be in-flight and pending should not be processed
|
|
347
|
+
expect(queue.inFlightCount).toBe(1);
|
|
348
|
+
expect(callback2Called).toBe(false);
|
|
349
|
+
expect(queue.pendingCount).toBe(1);
|
|
350
|
+
// Stop streaming
|
|
351
|
+
vi.spyOn(map1.core, "isStreaming").mockReturnValue(false);
|
|
352
|
+
// Now trackComplete should work
|
|
353
|
+
queue.trackComplete(map1.core);
|
|
354
|
+
expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
|
|
355
|
+
expect(callback2Called).toBe(true);
|
|
356
|
+
expect(queue.pendingCount).toBe(0);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
describe("trackUpdate", () => {
|
|
360
|
+
test("should refresh timeout for in-flight streaming CoValue", async () => {
|
|
361
|
+
vi.useFakeTimers();
|
|
362
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
363
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
364
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
365
|
+
const node = createTestNode();
|
|
366
|
+
const group = node.createGroup();
|
|
367
|
+
const map = group.createMap();
|
|
368
|
+
map.set("key", "value");
|
|
369
|
+
// Mock isStreaming to return true
|
|
370
|
+
vi.spyOn(map.core, "isStreaming").mockReturnValue(true);
|
|
371
|
+
queue.enqueue(map.core, () => { });
|
|
372
|
+
expect(queue.inFlightCount).toBe(1);
|
|
373
|
+
// Advance time to just before timeout
|
|
374
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
375
|
+
// Refresh the timeout (simulating receiving a chunk)
|
|
376
|
+
queue.trackUpdate(map.core);
|
|
377
|
+
// Advance another 200ms - would have timed out without the refresh
|
|
378
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
379
|
+
// Should still be in-flight because timeout was refreshed
|
|
380
|
+
expect(queue.inFlightCount).toBe(1);
|
|
381
|
+
// Now advance past the new timeout
|
|
382
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
383
|
+
// Should have timed out now
|
|
384
|
+
expect(queue.inFlightCount).toBe(0);
|
|
385
|
+
});
|
|
386
|
+
test("should be a no-op for unknown CoValues", () => {
|
|
387
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
388
|
+
const node = createTestNode();
|
|
389
|
+
const group = node.createGroup();
|
|
390
|
+
const map = group.createMap();
|
|
391
|
+
const unknownCoValue = node.getCoValue("co_zTestTrackUpdateUnknown01");
|
|
392
|
+
queue.enqueue(map.core, () => { });
|
|
393
|
+
expect(queue.inFlightCount).toBe(1);
|
|
394
|
+
// trackUpdate on unknown CoValue should be a no-op
|
|
395
|
+
queue.trackUpdate(unknownCoValue);
|
|
396
|
+
// Should still have the same in-flight count
|
|
397
|
+
expect(queue.inFlightCount).toBe(1);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
describe("immediate mode", () => {
|
|
401
|
+
test("should send immediately when mode is immediate even at capacity", () => {
|
|
402
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
403
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
404
|
+
const node = createTestNode();
|
|
405
|
+
const group = node.createGroup();
|
|
406
|
+
const map1 = group.createMap();
|
|
407
|
+
const map2 = group.createMap();
|
|
408
|
+
let callback1Called = false;
|
|
409
|
+
let callback2Called = false;
|
|
410
|
+
// Fill the queue to capacity
|
|
411
|
+
queue.enqueue(map1.core, () => {
|
|
412
|
+
callback1Called = true;
|
|
413
|
+
});
|
|
414
|
+
expect(callback1Called).toBe(true);
|
|
415
|
+
expect(queue.inFlightCount).toBe(1);
|
|
416
|
+
// This should bypass the capacity limit with immediate mode
|
|
417
|
+
queue.enqueue(map2.core, () => {
|
|
418
|
+
callback2Called = true;
|
|
419
|
+
}, "immediate");
|
|
420
|
+
// Both should have been called even though capacity is 1
|
|
421
|
+
expect(callback2Called).toBe(true);
|
|
422
|
+
expect(queue.inFlightCount).toBe(2);
|
|
423
|
+
});
|
|
424
|
+
test("should still track immediate requests for timeout handling", async () => {
|
|
425
|
+
vi.useFakeTimers();
|
|
426
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
427
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
428
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
429
|
+
const node = createTestNode();
|
|
430
|
+
const coValue = node.getCoValue("co_zTestOverflowTimeout01");
|
|
431
|
+
queue.enqueue(coValue, () => { }, "immediate");
|
|
432
|
+
expect(queue.inFlightCount).toBe(1);
|
|
433
|
+
// Advance time past the timeout
|
|
434
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
435
|
+
// Should be removed from in-flight and marked as not found
|
|
436
|
+
expect(queue.inFlightCount).toBe(0);
|
|
437
|
+
expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).toBe("unavailable");
|
|
438
|
+
});
|
|
439
|
+
test("should still deduplicate immediate requests", () => {
|
|
440
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
441
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
442
|
+
const node = createTestNode();
|
|
443
|
+
const group = node.createGroup();
|
|
444
|
+
const map = group.createMap();
|
|
445
|
+
let callbackCount = 0;
|
|
446
|
+
queue.enqueue(map.core, () => {
|
|
447
|
+
callbackCount++;
|
|
448
|
+
}, "immediate");
|
|
449
|
+
queue.enqueue(map.core, () => {
|
|
450
|
+
callbackCount++;
|
|
451
|
+
}, "immediate");
|
|
452
|
+
// Should only be called once due to deduplication
|
|
453
|
+
expect(callbackCount).toBe(1);
|
|
454
|
+
expect(queue.inFlightCount).toBe(1);
|
|
455
|
+
});
|
|
456
|
+
test("should process pending requests when immediate request completes", () => {
|
|
457
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
458
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
459
|
+
const node = createTestNode();
|
|
460
|
+
const group = node.createGroup();
|
|
461
|
+
const map1 = group.createMap();
|
|
462
|
+
const immediateMap = group.createMap();
|
|
463
|
+
const map2 = group.createMap();
|
|
464
|
+
let callback2Called = false;
|
|
465
|
+
// Fill the queue
|
|
466
|
+
queue.enqueue(map1.core, () => { });
|
|
467
|
+
// Add pending request
|
|
468
|
+
queue.enqueue(map2.core, () => {
|
|
469
|
+
callback2Called = true;
|
|
470
|
+
});
|
|
471
|
+
expect(callback2Called).toBe(false);
|
|
472
|
+
expect(queue.pendingCount).toBe(1);
|
|
473
|
+
// Add immediate request - this bypasses the queue but still counts as in-flight
|
|
474
|
+
queue.enqueue(immediateMap.core, () => { }, "immediate");
|
|
475
|
+
expect(queue.inFlightCount).toBe(2);
|
|
476
|
+
// Complete the immediate request
|
|
477
|
+
queue.trackComplete(immediateMap.core);
|
|
478
|
+
// Still at capacity because map1 is still in-flight
|
|
479
|
+
expect(callback2Called).toBe(false);
|
|
480
|
+
expect(queue.inFlightCount).toBe(1);
|
|
481
|
+
// Complete the regular request - now pending can be processed
|
|
482
|
+
queue.trackComplete(map1.core);
|
|
483
|
+
expect(callback2Called).toBe(true);
|
|
484
|
+
expect(queue.inFlightCount).toBe(1); // map2 is now in-flight
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
describe("clear", () => {
|
|
488
|
+
test("should clear all in-flight loads and pending queues", () => {
|
|
489
|
+
setMaxInFlightLoadsPerPeer(2);
|
|
490
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
491
|
+
const node = createTestNode();
|
|
492
|
+
const group = node.createGroup();
|
|
493
|
+
const map1 = group.createMap();
|
|
494
|
+
const map2 = group.createMap();
|
|
495
|
+
const map3 = group.createMap();
|
|
496
|
+
queue.enqueue(map1.core, () => { });
|
|
497
|
+
queue.enqueue(map2.core, () => { });
|
|
498
|
+
queue.enqueue(map3.core, () => { });
|
|
499
|
+
expect(queue.inFlightCount).toBe(2);
|
|
500
|
+
expect(queue.pendingCount).toBe(1);
|
|
501
|
+
queue.clear();
|
|
502
|
+
expect(queue.inFlightCount).toBe(0);
|
|
503
|
+
expect(queue.pendingCount).toBe(0);
|
|
504
|
+
});
|
|
505
|
+
test("should cancel any pending timeout", async () => {
|
|
506
|
+
vi.useFakeTimers();
|
|
507
|
+
CO_VALUE_LOADING_CONFIG.TIMEOUT = 1000;
|
|
508
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
509
|
+
const node = createTestNode();
|
|
510
|
+
const coValue = node.getCoValue("co_zTestClearTimeout00001");
|
|
511
|
+
queue.enqueue(coValue, () => { });
|
|
512
|
+
expect(queue.inFlightCount).toBe(1);
|
|
513
|
+
// Clear before timeout
|
|
514
|
+
queue.clear();
|
|
515
|
+
expect(queue.inFlightCount).toBe(0);
|
|
516
|
+
// Advance time past the timeout
|
|
517
|
+
await vi.advanceTimersByTimeAsync(1001);
|
|
518
|
+
// Should not have marked as not found since we cleared
|
|
519
|
+
expect(coValue.getLoadingStateForPeer(TEST_PEER_ID)).not.toBe("unavailable");
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
describe("priority queue", () => {
|
|
523
|
+
test("should process high-priority requests before low-priority", () => {
|
|
524
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
525
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
526
|
+
const node = createTestNode();
|
|
527
|
+
const group = node.createGroup();
|
|
528
|
+
// Block the queue first
|
|
529
|
+
const blockerMap = group.createMap();
|
|
530
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
531
|
+
// Enqueue low-priority requests
|
|
532
|
+
const lowMap1 = group.createMap();
|
|
533
|
+
const lowMap2 = group.createMap();
|
|
534
|
+
// Enqueue high-priority request
|
|
535
|
+
const highMap = group.createMap();
|
|
536
|
+
const order = [];
|
|
537
|
+
// Add low-priority first
|
|
538
|
+
queue.enqueue(lowMap1.core, () => order.push("low1"), "low-priority");
|
|
539
|
+
queue.enqueue(lowMap2.core, () => order.push("low2"), "low-priority");
|
|
540
|
+
// Add high-priority after
|
|
541
|
+
queue.enqueue(highMap.core, () => order.push("high"));
|
|
542
|
+
expect(queue.pendingCount).toBe(3);
|
|
543
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
544
|
+
expect(queue.lowPriorityPendingCount).toBe(2);
|
|
545
|
+
// Complete the blocker and process requests one by one
|
|
546
|
+
queue.trackComplete(blockerMap.core);
|
|
547
|
+
expect(order).toEqual(["high"]);
|
|
548
|
+
queue.trackComplete(highMap.core);
|
|
549
|
+
expect(order).toEqual(["high", "low1"]);
|
|
550
|
+
queue.trackComplete(lowMap1.core);
|
|
551
|
+
expect(order).toEqual(["high", "low1", "low2"]);
|
|
552
|
+
});
|
|
553
|
+
test("should add low-priority request to low-priority queue", () => {
|
|
554
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
555
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
556
|
+
const node = createTestNode();
|
|
557
|
+
const group = node.createGroup();
|
|
558
|
+
// Block the queue
|
|
559
|
+
const blockerMap = group.createMap();
|
|
560
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
561
|
+
const lowMap = group.createMap();
|
|
562
|
+
queue.enqueue(lowMap.core, () => { }, "low-priority");
|
|
563
|
+
expect(queue.pendingCount).toBe(1);
|
|
564
|
+
expect(queue.lowPriorityPendingCount).toBe(1);
|
|
565
|
+
expect(queue.highPriorityPendingCount).toBe(0);
|
|
566
|
+
});
|
|
567
|
+
test("should upgrade low-priority to high-priority when requested with normal priority", () => {
|
|
568
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
569
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
570
|
+
const node = createTestNode();
|
|
571
|
+
const group = node.createGroup();
|
|
572
|
+
// Block the queue
|
|
573
|
+
const blockerMap = group.createMap();
|
|
574
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
575
|
+
const targetMap = group.createMap();
|
|
576
|
+
let callbackCount = 0;
|
|
577
|
+
// First enqueue as low-priority
|
|
578
|
+
queue.enqueue(targetMap.core, () => {
|
|
579
|
+
callbackCount++;
|
|
580
|
+
}, "low-priority");
|
|
581
|
+
expect(queue.lowPriorityPendingCount).toBe(1);
|
|
582
|
+
expect(queue.highPriorityPendingCount).toBe(0);
|
|
583
|
+
// Upgrade by requesting with normal priority
|
|
584
|
+
queue.enqueue(targetMap.core, () => {
|
|
585
|
+
callbackCount += 10;
|
|
586
|
+
});
|
|
587
|
+
// Should have moved to high-priority queue
|
|
588
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
589
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
590
|
+
// Complete blocker and verify upgraded callback is used
|
|
591
|
+
queue.trackComplete(blockerMap.core);
|
|
592
|
+
expect(callbackCount).toBe(10); // Upgraded callback should be called
|
|
593
|
+
});
|
|
594
|
+
test("should not downgrade high-priority to low-priority", () => {
|
|
595
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
596
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
597
|
+
const node = createTestNode();
|
|
598
|
+
const group = node.createGroup();
|
|
599
|
+
// Block the queue
|
|
600
|
+
const blockerMap = group.createMap();
|
|
601
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
602
|
+
const targetMap = group.createMap();
|
|
603
|
+
// First enqueue as high-priority
|
|
604
|
+
queue.enqueue(targetMap.core, () => { });
|
|
605
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
606
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
607
|
+
// Try to enqueue as low-priority
|
|
608
|
+
queue.enqueue(targetMap.core, () => { }, "low-priority");
|
|
609
|
+
// Should still be in high-priority queue (no change)
|
|
610
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
611
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
612
|
+
});
|
|
613
|
+
test("should process mixed priority requests in correct order", () => {
|
|
614
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
615
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
616
|
+
const node = createTestNode();
|
|
617
|
+
const group = node.createGroup();
|
|
618
|
+
// Block the queue first
|
|
619
|
+
const blockerMap = group.createMap();
|
|
620
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
621
|
+
const order = [];
|
|
622
|
+
// Interleave low and high priority
|
|
623
|
+
const low1 = group.createMap();
|
|
624
|
+
const high1 = group.createMap();
|
|
625
|
+
const low2 = group.createMap();
|
|
626
|
+
const high2 = group.createMap();
|
|
627
|
+
const low3 = group.createMap();
|
|
628
|
+
queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
|
|
629
|
+
queue.enqueue(high1.core, () => order.push("high1"));
|
|
630
|
+
queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
|
|
631
|
+
queue.enqueue(high2.core, () => order.push("high2"));
|
|
632
|
+
queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
|
|
633
|
+
expect(queue.highPriorityPendingCount).toBe(2);
|
|
634
|
+
expect(queue.lowPriorityPendingCount).toBe(3);
|
|
635
|
+
// Process all
|
|
636
|
+
queue.trackComplete(blockerMap.core);
|
|
637
|
+
queue.trackComplete(high1.core);
|
|
638
|
+
queue.trackComplete(high2.core);
|
|
639
|
+
queue.trackComplete(low1.core);
|
|
640
|
+
queue.trackComplete(low2.core);
|
|
641
|
+
// High priority should come first, then low priority
|
|
642
|
+
expect(order).toEqual(["high1", "high2", "low1", "low2", "low3"]);
|
|
643
|
+
});
|
|
644
|
+
test("should handle upgrade when item is the only one in low-priority queue", () => {
|
|
645
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
646
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
647
|
+
const node = createTestNode();
|
|
648
|
+
const group = node.createGroup();
|
|
649
|
+
// Block the queue
|
|
650
|
+
const blockerMap = group.createMap();
|
|
651
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
652
|
+
const targetMap = group.createMap();
|
|
653
|
+
// Add single low-priority item
|
|
654
|
+
queue.enqueue(targetMap.core, () => { }, "low-priority");
|
|
655
|
+
expect(queue.lowPriorityPendingCount).toBe(1);
|
|
656
|
+
// Upgrade it
|
|
657
|
+
queue.enqueue(targetMap.core, () => { });
|
|
658
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
659
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
660
|
+
});
|
|
661
|
+
test("should handle upgrade when item is in the middle of low-priority queue", () => {
|
|
662
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
663
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
664
|
+
const node = createTestNode();
|
|
665
|
+
const group = node.createGroup();
|
|
666
|
+
// Block the queue
|
|
667
|
+
const blockerMap = group.createMap();
|
|
668
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
669
|
+
const order = [];
|
|
670
|
+
const low1 = group.createMap();
|
|
671
|
+
const low2 = group.createMap();
|
|
672
|
+
const low3 = group.createMap();
|
|
673
|
+
// Add three low-priority items
|
|
674
|
+
queue.enqueue(low1.core, () => order.push("low1"), "low-priority");
|
|
675
|
+
queue.enqueue(low2.core, () => order.push("low2"), "low-priority");
|
|
676
|
+
queue.enqueue(low3.core, () => order.push("low3"), "low-priority");
|
|
677
|
+
expect(queue.lowPriorityPendingCount).toBe(3);
|
|
678
|
+
// Upgrade the middle one (uses new callback)
|
|
679
|
+
queue.enqueue(low2.core, () => order.push("upgraded"));
|
|
680
|
+
expect(queue.lowPriorityPendingCount).toBe(2);
|
|
681
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
682
|
+
// Process all
|
|
683
|
+
queue.trackComplete(blockerMap.core);
|
|
684
|
+
queue.trackComplete(low2.core);
|
|
685
|
+
queue.trackComplete(low1.core);
|
|
686
|
+
// upgraded should be first (high priority with new callback), then low1 and low3
|
|
687
|
+
expect(order).toEqual(["upgraded", "low1", "low3"]);
|
|
688
|
+
});
|
|
689
|
+
test("should execute immediately when upgrading low-priority with immediate mode", () => {
|
|
690
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
691
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
692
|
+
const node = createTestNode();
|
|
693
|
+
const group = node.createGroup();
|
|
694
|
+
// Block the queue
|
|
695
|
+
const blockerMap = group.createMap();
|
|
696
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
697
|
+
const targetMap = group.createMap();
|
|
698
|
+
let originalCallbackCalled = false;
|
|
699
|
+
let immediateCallbackCalled = false;
|
|
700
|
+
// Add low-priority item
|
|
701
|
+
queue.enqueue(targetMap.core, () => {
|
|
702
|
+
originalCallbackCalled = true;
|
|
703
|
+
}, "low-priority");
|
|
704
|
+
expect(queue.lowPriorityPendingCount).toBe(1);
|
|
705
|
+
expect(originalCallbackCalled).toBe(false);
|
|
706
|
+
// Upgrade with immediate mode - should execute immediately with new callback
|
|
707
|
+
queue.enqueue(targetMap.core, () => {
|
|
708
|
+
immediateCallbackCalled = true;
|
|
709
|
+
}, "immediate");
|
|
710
|
+
// Should have been executed immediately, not moved to high-priority queue
|
|
711
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
712
|
+
expect(queue.highPriorityPendingCount).toBe(0);
|
|
713
|
+
expect(originalCallbackCalled).toBe(false); // Original callback not called
|
|
714
|
+
expect(immediateCallbackCalled).toBe(true); // New callback called
|
|
715
|
+
// Should be in-flight now (2 because blocker is also in-flight)
|
|
716
|
+
expect(queue.inFlightCount).toBe(2);
|
|
717
|
+
});
|
|
718
|
+
test("should execute immediately when upgrading high-priority with immediate mode", () => {
|
|
719
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
720
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
721
|
+
const node = createTestNode();
|
|
722
|
+
const group = node.createGroup();
|
|
723
|
+
// Block the queue
|
|
724
|
+
const blockerMap = group.createMap();
|
|
725
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
726
|
+
const targetMap = group.createMap();
|
|
727
|
+
let originalCallbackCalled = false;
|
|
728
|
+
let immediateCallbackCalled = false;
|
|
729
|
+
// Add high-priority item (it will be queued since blocker is in-flight)
|
|
730
|
+
queue.enqueue(targetMap.core, () => {
|
|
731
|
+
originalCallbackCalled = true;
|
|
732
|
+
});
|
|
733
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
734
|
+
expect(originalCallbackCalled).toBe(false);
|
|
735
|
+
// Request with immediate mode - should execute immediately with new callback
|
|
736
|
+
queue.enqueue(targetMap.core, () => {
|
|
737
|
+
immediateCallbackCalled = true;
|
|
738
|
+
}, "immediate");
|
|
739
|
+
// Should have been executed immediately, removed from high-priority queue
|
|
740
|
+
expect(queue.highPriorityPendingCount).toBe(0);
|
|
741
|
+
expect(originalCallbackCalled).toBe(false); // Original callback not called
|
|
742
|
+
expect(immediateCallbackCalled).toBe(true); // New callback called
|
|
743
|
+
// Should be in-flight now (2 because blocker is also in-flight)
|
|
744
|
+
expect(queue.inFlightCount).toBe(2);
|
|
745
|
+
});
|
|
746
|
+
test("should skip immediate request on an in-flight value", () => {
|
|
747
|
+
setMaxInFlightLoadsPerPeer(10);
|
|
748
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
749
|
+
const node = createTestNode();
|
|
750
|
+
const group = node.createGroup();
|
|
751
|
+
const targetMap = group.createMap();
|
|
752
|
+
let firstCallbackCount = 0;
|
|
753
|
+
let secondCallbackCount = 0;
|
|
754
|
+
// First enqueue - should go in-flight immediately
|
|
755
|
+
queue.enqueue(targetMap.core, () => {
|
|
756
|
+
firstCallbackCount++;
|
|
757
|
+
});
|
|
758
|
+
expect(queue.inFlightCount).toBe(1);
|
|
759
|
+
expect(firstCallbackCount).toBe(1);
|
|
760
|
+
// Immediate request on in-flight value - should be skipped
|
|
761
|
+
queue.enqueue(targetMap.core, () => {
|
|
762
|
+
secondCallbackCount++;
|
|
763
|
+
}, "immediate");
|
|
764
|
+
// Should not have sent again
|
|
765
|
+
expect(queue.inFlightCount).toBe(1);
|
|
766
|
+
expect(firstCallbackCount).toBe(1);
|
|
767
|
+
expect(secondCallbackCount).toBe(0);
|
|
768
|
+
});
|
|
769
|
+
test("should skip low-priority request on an in-flight value", () => {
|
|
770
|
+
setMaxInFlightLoadsPerPeer(10);
|
|
771
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
772
|
+
const node = createTestNode();
|
|
773
|
+
const group = node.createGroup();
|
|
774
|
+
const targetMap = group.createMap();
|
|
775
|
+
let firstCallbackCount = 0;
|
|
776
|
+
let secondCallbackCount = 0;
|
|
777
|
+
// First enqueue - should go in-flight immediately
|
|
778
|
+
queue.enqueue(targetMap.core, () => {
|
|
779
|
+
firstCallbackCount++;
|
|
780
|
+
});
|
|
781
|
+
expect(queue.inFlightCount).toBe(1);
|
|
782
|
+
expect(firstCallbackCount).toBe(1);
|
|
783
|
+
// Low-priority request on in-flight value - should be skipped
|
|
784
|
+
queue.enqueue(targetMap.core, () => {
|
|
785
|
+
secondCallbackCount++;
|
|
786
|
+
}, "low-priority");
|
|
787
|
+
// Should not have sent again or queued
|
|
788
|
+
expect(queue.inFlightCount).toBe(1);
|
|
789
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
790
|
+
expect(firstCallbackCount).toBe(1);
|
|
791
|
+
expect(secondCallbackCount).toBe(0);
|
|
792
|
+
});
|
|
793
|
+
test("should clear both priority queues on clear()", () => {
|
|
794
|
+
setMaxInFlightLoadsPerPeer(1);
|
|
795
|
+
const queue = new OutgoingLoadQueue(TEST_PEER_ID);
|
|
796
|
+
const node = createTestNode();
|
|
797
|
+
const group = node.createGroup();
|
|
798
|
+
// Block the queue
|
|
799
|
+
const blockerMap = group.createMap();
|
|
800
|
+
queue.enqueue(blockerMap.core, () => { });
|
|
801
|
+
const highMap = group.createMap();
|
|
802
|
+
const lowMap = group.createMap();
|
|
803
|
+
queue.enqueue(highMap.core, () => { });
|
|
804
|
+
queue.enqueue(lowMap.core, () => { }, "low-priority");
|
|
805
|
+
expect(queue.highPriorityPendingCount).toBe(1);
|
|
806
|
+
expect(queue.lowPriorityPendingCount).toBe(1);
|
|
807
|
+
queue.clear();
|
|
808
|
+
expect(queue.highPriorityPendingCount).toBe(0);
|
|
809
|
+
expect(queue.lowPriorityPendingCount).toBe(0);
|
|
810
|
+
expect(queue.pendingCount).toBe(0);
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
//# sourceMappingURL=OutgoingLoadQueue.test.js.map
|