@minnowdb/core 0.9.1 → 0.10.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/dist/engine/auto-store.d.ts +52 -0
- package/dist/engine/auto-store.js +157 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client-audit-harness.js +123 -0
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +176 -46
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1276 -251
- package/dist/engine/errors.d.ts +61 -2
- package/dist/engine/errors.js +116 -3
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/live.d.ts +24 -1
- package/dist/engine/live.js +33 -9
- package/dist/engine/scope-write-set.js +36 -0
- package/dist/engine/worker-auto.d.ts +1 -0
- package/dist/engine/worker-auto.js +3 -0
- package/dist/engine/worker-host.d.ts +2 -1
- package/dist/engine/worker-host.js +19 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +122 -19
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +44 -2
- package/dist/storage/indexeddb-audit-helpers.js +269 -0
- package/dist/storage/indexeddb.js +599 -374
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +243 -17
- package/dist/storage/opfs/power-loss-model.js +62 -0
- package/dist/storage/opfs/rpc.js +24 -43
- package/dist/storage/opfs/store.d.ts +32 -0
- package/dist/storage/opfs/store.js +531 -65
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +4 -4
- package/dist/storage/types.d.ts +31 -10
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +19 -0
- package/dist/transactions/index.js +99 -25
- package/dist/worker-protocol/index.d.ts +50 -2
- package/dist/worker-protocol/index.js +106 -4
- package/package.json +7 -2
|
@@ -2,12 +2,16 @@ import { assertStorageBulkReadItems, assertTempRunPageBatchLimits, OpfsCoordinat
|
|
|
2
2
|
import { validateTempRunPage, validateTempRunPageIdentity } from "../toolkit/record-core.js";
|
|
3
3
|
import { OpfsTree, encodeSegment, isDomError } from "./files.js";
|
|
4
4
|
import { LOG_FORMAT_VERSION } from "../toolkit/wire.js";
|
|
5
|
-
import { OpfsLeader } from "./leader.js";
|
|
6
|
-
import { rehydrateStoreError, estimateRpcValueBytes, fingerprintStoreRequest, MAX_OPFS_RPC_MESSAGE_BYTES, parseStoreRpcMessage, serializeStoreError } from "./rpc.js";
|
|
5
|
+
import { OpfsLeader, OpfsLeaderClosedError } from "./leader.js";
|
|
6
|
+
import { rehydrateStoreError, estimateRpcValueBytes, fingerprintStoreRequest, MAX_OPFS_RPC_HOLD_MS, MAX_OPFS_RPC_MESSAGE_BYTES, parseStoreRpcMessage, serializeStoreError } from "./rpc.js";
|
|
7
7
|
const RPC_TIMEOUT_MS = 1e3;
|
|
8
8
|
const DISCOVERY_WAIT_MS = 150;
|
|
9
|
-
const
|
|
9
|
+
const DISPATCH_BUDGET_MS = 1e4;
|
|
10
10
|
const YIELD_COOLDOWN_MS = 3e3;
|
|
11
|
+
const HANDOVER_GRACE_MS = 1500;
|
|
12
|
+
const HIDDEN_IDLE_RELEASE_MS = 15e3;
|
|
13
|
+
const MUTATION_PATIENCE_ROUNDS = 3;
|
|
14
|
+
const DECLINE_AFTER_CLOSE_MS = 1e3;
|
|
11
15
|
const DEDUPE_CACHE_SIZE = 512;
|
|
12
16
|
const RPC_IN_FLIGHT_LIMIT = 512;
|
|
13
17
|
const RPC_IN_FLIGHT_MUTATION_BYTES = 128 * 1024 * 1024;
|
|
@@ -175,7 +179,14 @@ class OpfsBlockStore {
|
|
|
175
179
|
#durability;
|
|
176
180
|
#checkpointEntries;
|
|
177
181
|
#cleanupLimitBytes;
|
|
182
|
+
#servedLedgerAgeMs;
|
|
183
|
+
#servedLedgerResultBytes;
|
|
178
184
|
#rpcTimeoutMs;
|
|
185
|
+
#yieldCooldownMs;
|
|
186
|
+
#hiddenIdleReleaseMs;
|
|
187
|
+
#handoverGraceMs;
|
|
188
|
+
#dispatchBudgetMs;
|
|
189
|
+
#onDiagnostic;
|
|
179
190
|
#instanceId = crypto.randomUUID();
|
|
180
191
|
#channelName;
|
|
181
192
|
#channel;
|
|
@@ -187,6 +198,8 @@ class OpfsBlockStore {
|
|
|
187
198
|
#closed = false;
|
|
188
199
|
#lastYieldAt = 0;
|
|
189
200
|
#electing;
|
|
201
|
+
#recovering = false;
|
|
202
|
+
#waitHeardAt = 0;
|
|
190
203
|
#pending = /* @__PURE__ */ new Map();
|
|
191
204
|
#inFlightMutations = /* @__PURE__ */ new Map();
|
|
192
205
|
#settledMutations = /* @__PURE__ */ new Map();
|
|
@@ -201,12 +214,27 @@ class OpfsBlockStore {
|
|
|
201
214
|
#servedMutationGateForTests;
|
|
202
215
|
#dropNextRpcResultForTests = false;
|
|
203
216
|
#reacquireTimer;
|
|
217
|
+
#served = /* @__PURE__ */ new Map();
|
|
218
|
+
#keepaliveTimer;
|
|
219
|
+
#deferredBid;
|
|
220
|
+
#hiddenIdleTimer;
|
|
221
|
+
#inboxLingerTimer;
|
|
222
|
+
#lastActivityAt = Date.now();
|
|
223
|
+
#othersSeen = false;
|
|
224
|
+
#gracefulLeaders = /* @__PURE__ */ new Set();
|
|
204
225
|
constructor(tree, options) {
|
|
205
226
|
this.#tree = tree;
|
|
206
227
|
this.#durability = options.durability ?? "strict";
|
|
207
228
|
this.#checkpointEntries = options.checkpointEntries;
|
|
208
229
|
this.#cleanupLimitBytes = options.cleanupLimitBytes;
|
|
230
|
+
this.#servedLedgerAgeMs = options.servedLedgerAgeMs;
|
|
231
|
+
this.#servedLedgerResultBytes = options.servedLedgerResultBytes;
|
|
209
232
|
this.#rpcTimeoutMs = options.rpcTimeoutMs ?? RPC_TIMEOUT_MS;
|
|
233
|
+
this.#yieldCooldownMs = options.yieldCooldownMs ?? YIELD_COOLDOWN_MS;
|
|
234
|
+
this.#hiddenIdleReleaseMs = options.hiddenIdleReleaseMs ?? HIDDEN_IDLE_RELEASE_MS;
|
|
235
|
+
this.#handoverGraceMs = options.handoverGraceMs ?? HANDOVER_GRACE_MS;
|
|
236
|
+
this.#dispatchBudgetMs = options.dispatchBudgetMs ?? DISPATCH_BUDGET_MS;
|
|
237
|
+
this.#onDiagnostic = options.onDiagnostic;
|
|
210
238
|
this.#channelName = `minnowdb-store:${options.name}`;
|
|
211
239
|
this.liveQueryChannelName = `minnowdb-live:opfs:${options.name}`;
|
|
212
240
|
}
|
|
@@ -246,11 +274,26 @@ class OpfsBlockStore {
|
|
|
246
274
|
throw error;
|
|
247
275
|
}
|
|
248
276
|
}
|
|
249
|
-
|
|
277
|
+
#diagnostic(error, context) {
|
|
278
|
+
try {
|
|
279
|
+
this.#onDiagnostic?.(error, context);
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
#electInBackground(context, force = false) {
|
|
284
|
+
this.#tryBecomeLeader(force).catch((error) => {
|
|
285
|
+
this.#diagnostic(error, context);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async #tryBecomeLeader(force = false) {
|
|
250
289
|
if (this.#closed)
|
|
251
290
|
return false;
|
|
252
291
|
if (this.#leader !== void 0)
|
|
253
292
|
return true;
|
|
293
|
+
if (this.#yielding !== void 0)
|
|
294
|
+
return false;
|
|
295
|
+
if (!force && Date.now() - this.#lastYieldAt < this.#handoverGraceMs)
|
|
296
|
+
return false;
|
|
254
297
|
if (this.#electing !== void 0)
|
|
255
298
|
return this.#electing;
|
|
256
299
|
const election = this.#elect();
|
|
@@ -272,10 +315,12 @@ class OpfsBlockStore {
|
|
|
272
315
|
}
|
|
273
316
|
let slotA;
|
|
274
317
|
let slotB;
|
|
318
|
+
this.#recovering = true;
|
|
275
319
|
try {
|
|
320
|
+
this.#post({ kind: "wait", leaderId: this.#instanceId });
|
|
276
321
|
slotA = await this.#openWithRetry(["checkpoint-a"]);
|
|
277
322
|
slotB = await this.#openWithRetry(["checkpoint-b"]);
|
|
278
|
-
this.#leader = await OpfsLeader.recover(this.#tree, this.#durability === "strict", { wal, slotA, slotB }, this.#checkpointEntries, this.#cleanupLimitBytes);
|
|
323
|
+
this.#leader = await OpfsLeader.recover(this.#tree, this.#durability === "strict", { wal, slotA, slotB }, this.#checkpointEntries, this.#cleanupLimitBytes, this.#onDiagnostic, this.#servedLedgerAgeMs, this.#servedLedgerResultBytes);
|
|
279
324
|
} catch (error) {
|
|
280
325
|
wal.close();
|
|
281
326
|
slotA?.close();
|
|
@@ -283,19 +328,108 @@ class OpfsBlockStore {
|
|
|
283
328
|
if (isLockContention(error))
|
|
284
329
|
return false;
|
|
285
330
|
throw error;
|
|
331
|
+
} finally {
|
|
332
|
+
this.#recovering = false;
|
|
286
333
|
}
|
|
287
334
|
if (this.#closed) {
|
|
288
335
|
const leader = this.#leader;
|
|
289
336
|
this.#leader = void 0;
|
|
290
|
-
await leader.shutdown().catch(() => {
|
|
337
|
+
await leader.shutdown().catch((error) => {
|
|
338
|
+
this.#diagnostic(error, "opfs shutdown after close during election");
|
|
291
339
|
leader.crash();
|
|
292
340
|
});
|
|
293
341
|
return false;
|
|
294
342
|
}
|
|
295
343
|
this.#knownLeader = this.#instanceId;
|
|
344
|
+
this.#leader.onBeforeCheckpoint = (expectedMs) => {
|
|
345
|
+
this.#holdServed(expectedMs);
|
|
346
|
+
};
|
|
296
347
|
this.#post({ kind: "leader", leaderId: this.#instanceId });
|
|
348
|
+
this.#post({ kind: "state", leaderId: this.#instanceId, foreground: this.#foreground });
|
|
349
|
+
if (!this.#foreground)
|
|
350
|
+
this.#armHiddenIdleTimer();
|
|
297
351
|
return true;
|
|
298
352
|
}
|
|
353
|
+
#holdServed(expectedMs) {
|
|
354
|
+
if (this.#served.size === 0)
|
|
355
|
+
return;
|
|
356
|
+
const ms = Math.min(MAX_OPFS_RPC_HOLD_MS, expectedMs * 2 + this.#rpcTimeoutMs);
|
|
357
|
+
for (const [requestId, from] of this.#served)
|
|
358
|
+
this.#answer(from, { kind: "hold", requestId, ms });
|
|
359
|
+
}
|
|
360
|
+
#keepaliveSuppressedForTests = false;
|
|
361
|
+
_suppressKeepaliveForTests() {
|
|
362
|
+
this.#keepaliveSuppressedForTests = true;
|
|
363
|
+
this.#stopKeepalive();
|
|
364
|
+
}
|
|
365
|
+
#startKeepalive() {
|
|
366
|
+
if (this.#keepaliveTimer !== void 0 || this.#keepaliveSuppressedForTests)
|
|
367
|
+
return;
|
|
368
|
+
const timer = setInterval(() => {
|
|
369
|
+
if (this.#served.size === 0) {
|
|
370
|
+
clearInterval(timer);
|
|
371
|
+
if (this.#keepaliveTimer === timer)
|
|
372
|
+
this.#keepaliveTimer = void 0;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const [requestId, from] of this.#served) {
|
|
376
|
+
this.#answer(from, { kind: "busy", requestId });
|
|
377
|
+
}
|
|
378
|
+
}, Math.max(1, Math.floor(this.#rpcTimeoutMs / 2)));
|
|
379
|
+
timer.unref?.();
|
|
380
|
+
this.#keepaliveTimer = timer;
|
|
381
|
+
}
|
|
382
|
+
#stopKeepalive() {
|
|
383
|
+
if (this.#keepaliveTimer === void 0)
|
|
384
|
+
return;
|
|
385
|
+
clearInterval(this.#keepaliveTimer);
|
|
386
|
+
this.#keepaliveTimer = void 0;
|
|
387
|
+
}
|
|
388
|
+
#armHiddenIdleTimer() {
|
|
389
|
+
this.#clearHiddenIdleTimer();
|
|
390
|
+
if (this.#foreground || this.#leader === void 0 || this.#closed)
|
|
391
|
+
return;
|
|
392
|
+
const elapsed = Date.now() - this.#lastActivityAt;
|
|
393
|
+
const timer = setTimeout(() => {
|
|
394
|
+
this.#hiddenIdleTimer = void 0;
|
|
395
|
+
if (this.#closed || this.#leader === void 0 || this.#foreground)
|
|
396
|
+
return;
|
|
397
|
+
if (this.#othersSeen && this.#served.size === 0 && this.#yielding === void 0 && Date.now() - this.#lastActivityAt >= this.#hiddenIdleReleaseMs) {
|
|
398
|
+
this.#demote().catch((error) => {
|
|
399
|
+
this.#diagnostic(error, "opfs idle release");
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.#armHiddenIdleTimer();
|
|
404
|
+
}, Math.max(0, this.#hiddenIdleReleaseMs - elapsed));
|
|
405
|
+
timer.unref?.();
|
|
406
|
+
this.#hiddenIdleTimer = timer;
|
|
407
|
+
}
|
|
408
|
+
#clearHiddenIdleTimer() {
|
|
409
|
+
if (this.#hiddenIdleTimer === void 0)
|
|
410
|
+
return;
|
|
411
|
+
clearTimeout(this.#hiddenIdleTimer);
|
|
412
|
+
this.#hiddenIdleTimer = void 0;
|
|
413
|
+
}
|
|
414
|
+
async #demote() {
|
|
415
|
+
const leader = this.#leader;
|
|
416
|
+
if (leader === void 0)
|
|
417
|
+
return;
|
|
418
|
+
this.#leader = void 0;
|
|
419
|
+
this.#knownLeader = void 0;
|
|
420
|
+
const shutdown = this.#shutdownAfterMutations(leader).catch((error) => {
|
|
421
|
+
this.#diagnostic(error, "opfs idle release shutdown");
|
|
422
|
+
leader.crash();
|
|
423
|
+
});
|
|
424
|
+
this.#yielding = shutdown;
|
|
425
|
+
await shutdown;
|
|
426
|
+
if (this.#yielding === shutdown)
|
|
427
|
+
this.#yielding = void 0;
|
|
428
|
+
if (this.#closed)
|
|
429
|
+
return;
|
|
430
|
+
this.#closeAnswerChannels();
|
|
431
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
432
|
+
}
|
|
299
433
|
async #openWithRetry(path) {
|
|
300
434
|
for (let attempt = 0; ; attempt += 1) {
|
|
301
435
|
try {
|
|
@@ -310,19 +444,66 @@ class OpfsBlockStore {
|
|
|
310
444
|
}
|
|
311
445
|
}
|
|
312
446
|
setForeground(foreground) {
|
|
447
|
+
if (this.#closed)
|
|
448
|
+
return;
|
|
449
|
+
const changed = this.#foreground !== foreground;
|
|
313
450
|
this.#foreground = foreground;
|
|
314
|
-
if (
|
|
451
|
+
if (this.#leader !== void 0) {
|
|
452
|
+
if (foreground) {
|
|
453
|
+
this.#clearHiddenIdleTimer();
|
|
454
|
+
this.#clearDeferredBid();
|
|
455
|
+
} else {
|
|
456
|
+
this.#lastActivityAt = Date.now();
|
|
457
|
+
this.#armHiddenIdleTimer();
|
|
458
|
+
}
|
|
459
|
+
if (changed)
|
|
460
|
+
this.#post({ kind: "state", leaderId: this.#instanceId, foreground });
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (foreground)
|
|
315
464
|
this.#post({ kind: "bid", bidderId: this.#instanceId, foreground: true });
|
|
465
|
+
}
|
|
466
|
+
#clearDeferredBid() {
|
|
467
|
+
if (this.#deferredBid === void 0)
|
|
468
|
+
return;
|
|
469
|
+
clearTimeout(this.#deferredBid.timer);
|
|
470
|
+
this.#deferredBid = void 0;
|
|
471
|
+
}
|
|
472
|
+
#considerBid(bidderId) {
|
|
473
|
+
if (this.#leader === void 0 || this.#foreground || this.#yielding !== void 0)
|
|
474
|
+
return;
|
|
475
|
+
const remaining = this.#yieldCooldownMs - (Date.now() - this.#lastYieldAt);
|
|
476
|
+
if (remaining <= 0) {
|
|
477
|
+
this.#clearDeferredBid();
|
|
478
|
+
this.#yieldLeadership(bidderId).catch((error) => {
|
|
479
|
+
this.#diagnostic(error, "opfs yield");
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
316
482
|
}
|
|
483
|
+
if (this.#deferredBid !== void 0) {
|
|
484
|
+
this.#deferredBid.bidderId = bidderId;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const timer = setTimeout(() => {
|
|
488
|
+
const deferred = this.#deferredBid;
|
|
489
|
+
this.#deferredBid = void 0;
|
|
490
|
+
if (deferred !== void 0 && !this.#closed)
|
|
491
|
+
this.#considerBid(deferred.bidderId);
|
|
492
|
+
}, remaining);
|
|
493
|
+
timer.unref?.();
|
|
494
|
+
this.#deferredBid = { bidderId, timer };
|
|
317
495
|
}
|
|
318
496
|
async #yieldLeadership(to) {
|
|
319
497
|
const leader = this.#leader;
|
|
320
498
|
if (leader === void 0)
|
|
321
499
|
return;
|
|
500
|
+
this.#clearHiddenIdleTimer();
|
|
501
|
+
this.#clearDeferredBid();
|
|
322
502
|
this.#leader = void 0;
|
|
323
503
|
this.#knownLeader = void 0;
|
|
324
504
|
this.#lastYieldAt = Date.now();
|
|
325
|
-
const shutdown = leader
|
|
505
|
+
const shutdown = this.#shutdownAfterMutations(leader).catch((error) => {
|
|
506
|
+
this.#diagnostic(error, "opfs yield shutdown");
|
|
326
507
|
leader.crash();
|
|
327
508
|
});
|
|
328
509
|
this.#yielding = shutdown;
|
|
@@ -333,22 +514,40 @@ class OpfsBlockStore {
|
|
|
333
514
|
return;
|
|
334
515
|
this.#closeAnswerChannels();
|
|
335
516
|
this.#post({ kind: "yield", to });
|
|
517
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
336
518
|
if (this.#reacquireTimer !== void 0)
|
|
337
519
|
clearTimeout(this.#reacquireTimer);
|
|
338
520
|
this.#reacquireTimer = setTimeout(() => {
|
|
339
|
-
if (this.#knownLeader === void 0 && !this.#closed)
|
|
340
|
-
|
|
341
|
-
|
|
521
|
+
if (this.#knownLeader === void 0 && !this.#closed) {
|
|
522
|
+
this.#electInBackground("opfs reacquire after yield", true);
|
|
523
|
+
}
|
|
524
|
+
}, this.#handoverGraceMs);
|
|
342
525
|
}
|
|
343
526
|
#yielding;
|
|
527
|
+
#shutdownAfterMutations(leader) {
|
|
528
|
+
return this.#withMutationTurn(() => leader.shutdown());
|
|
529
|
+
}
|
|
530
|
+
async #servedDrained() {
|
|
531
|
+
const deadline = Date.now() + DECLINE_AFTER_CLOSE_MS;
|
|
532
|
+
while (this.#served.size > 0 && Date.now() < deadline)
|
|
533
|
+
await sleep(5);
|
|
534
|
+
}
|
|
344
535
|
#onMessage(message) {
|
|
345
|
-
if (this.#closed)
|
|
536
|
+
if (this.#closed) {
|
|
537
|
+
if (message.kind === "op")
|
|
538
|
+
this.#decline(message);
|
|
346
539
|
return;
|
|
540
|
+
}
|
|
347
541
|
if (this.#coordinationPausedForTests)
|
|
348
542
|
return;
|
|
543
|
+
this.#othersSeen = true;
|
|
349
544
|
switch (message.kind) {
|
|
350
545
|
case "op": {
|
|
351
|
-
if (this.#leader
|
|
546
|
+
if (this.#leader === void 0) {
|
|
547
|
+
this.#decline(message);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
{
|
|
352
551
|
const requestBytes = estimateRpcValueBytes(message.args);
|
|
353
552
|
if (this.#servedRequestCount >= RPC_SERVER_ADMISSION_LIMIT || this.#servedRequestBytes + requestBytes > RPC_SERVER_ADMISSION_BYTES) {
|
|
354
553
|
this.#answer(message.from, {
|
|
@@ -361,13 +560,51 @@ class OpfsBlockStore {
|
|
|
361
560
|
}
|
|
362
561
|
this.#servedRequestCount += 1;
|
|
363
562
|
this.#servedRequestBytes += requestBytes;
|
|
364
|
-
|
|
563
|
+
this.#lastActivityAt = Date.now();
|
|
564
|
+
const alreadyServing = this.#served.has(message.requestId);
|
|
565
|
+
this.#served.set(message.requestId, message.from);
|
|
566
|
+
this.#startKeepalive();
|
|
567
|
+
void this.#serveOp(message).catch((error) => {
|
|
568
|
+
this.#diagnostic(error, `opfs served ${message.method}`);
|
|
569
|
+
this.#answer(message.from, {
|
|
570
|
+
kind: "result",
|
|
571
|
+
requestId: message.requestId,
|
|
572
|
+
ok: false,
|
|
573
|
+
error: serializeStoreError(error)
|
|
574
|
+
});
|
|
575
|
+
}).finally(() => {
|
|
365
576
|
this.#servedRequestCount = Math.max(0, this.#servedRequestCount - 1);
|
|
366
577
|
this.#servedRequestBytes = Math.max(0, this.#servedRequestBytes - requestBytes);
|
|
578
|
+
if (!alreadyServing)
|
|
579
|
+
this.#served.delete(message.requestId);
|
|
580
|
+
this.#lastActivityAt = Date.now();
|
|
367
581
|
});
|
|
368
582
|
}
|
|
369
583
|
return;
|
|
370
584
|
}
|
|
585
|
+
case "declined": {
|
|
586
|
+
const pending = this.#takePending(message.requestId);
|
|
587
|
+
if (pending === void 0)
|
|
588
|
+
return;
|
|
589
|
+
clearTimeout(pending.timer);
|
|
590
|
+
pending.reject(RPC_DECLINED);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
case "uncertain": {
|
|
594
|
+
const pending = this.#takePending(message.requestId);
|
|
595
|
+
if (pending === void 0)
|
|
596
|
+
return;
|
|
597
|
+
clearTimeout(pending.timer);
|
|
598
|
+
pending.reject(new OpfsUncertainOutcomeError(pending.message.method));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
case "hold": {
|
|
602
|
+
const pending = this.#pending.get(message.requestId);
|
|
603
|
+
if (pending === void 0)
|
|
604
|
+
return;
|
|
605
|
+
this.#armPendingTimer(pending, Math.max(message.ms, this.#rpcTimeoutMs));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
371
608
|
case "result": {
|
|
372
609
|
const pending = this.#takePending(message.requestId);
|
|
373
610
|
if (pending === void 0)
|
|
@@ -383,26 +620,28 @@ class OpfsBlockStore {
|
|
|
383
620
|
const pending = this.#pending.get(message.requestId);
|
|
384
621
|
if (pending === void 0)
|
|
385
622
|
return;
|
|
386
|
-
|
|
387
|
-
pending.timer = setTimeout(() => {
|
|
388
|
-
const expired = this.#takePending(message.requestId);
|
|
389
|
-
expired?.reject(RPC_TIMED_OUT);
|
|
390
|
-
}, this.#rpcTimeoutMs);
|
|
623
|
+
this.#armPendingTimer(pending, this.#rpcTimeoutMs);
|
|
391
624
|
return;
|
|
392
625
|
}
|
|
393
626
|
case "leader": {
|
|
394
627
|
this.#knownLeader = message.leaderId;
|
|
628
|
+
this.#gracefulLeaders.delete(message.leaderId);
|
|
395
629
|
if (this.#reacquireTimer !== void 0) {
|
|
396
630
|
clearTimeout(this.#reacquireTimer);
|
|
397
631
|
this.#reacquireTimer = void 0;
|
|
398
632
|
}
|
|
399
|
-
for (const
|
|
400
|
-
if (pending.sentTo === message.leaderId)
|
|
633
|
+
for (const pending of this.#pending.values()) {
|
|
634
|
+
if (pending.sentTo === message.leaderId) {
|
|
635
|
+
if (pending.awaitingPing)
|
|
636
|
+
this.#armPendingTimer(pending, this.#rpcTimeoutMs);
|
|
401
637
|
continue;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
638
|
+
}
|
|
639
|
+
if (pending.sentTo === void 0) {
|
|
640
|
+
pending.sentTo = message.leaderId;
|
|
641
|
+
this.#send(message.leaderId, pending.message);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (!READ_METHODS.has(pending.message.method) && this.#gracefulLeaders.has(pending.sentTo)) {
|
|
406
645
|
continue;
|
|
407
646
|
}
|
|
408
647
|
pending.sentTo = message.leaderId;
|
|
@@ -413,30 +652,81 @@ class OpfsBlockStore {
|
|
|
413
652
|
}
|
|
414
653
|
return;
|
|
415
654
|
}
|
|
655
|
+
case "state": {
|
|
656
|
+
if (!message.foreground && this.#foreground && this.#leader === void 0 && message.leaderId !== this.#instanceId) {
|
|
657
|
+
this.#post({ kind: "bid", bidderId: this.#instanceId, foreground: true });
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
416
661
|
case "ping": {
|
|
417
662
|
if (this.#leader !== void 0) {
|
|
418
663
|
this.#post({ kind: "leader", leaderId: this.#instanceId });
|
|
664
|
+
} else if (this.#recovering || this.#yielding !== void 0) {
|
|
665
|
+
this.#post({ kind: "wait", leaderId: this.#instanceId });
|
|
419
666
|
}
|
|
420
667
|
return;
|
|
421
668
|
}
|
|
669
|
+
case "wait": {
|
|
670
|
+
if (message.leaderId !== this.#instanceId)
|
|
671
|
+
this.#waitHeardAt = Date.now();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
422
674
|
case "bid": {
|
|
423
|
-
if (
|
|
424
|
-
|
|
675
|
+
if (message.foreground && message.bidderId !== this.#instanceId) {
|
|
676
|
+
this.#considerBid(message.bidderId);
|
|
425
677
|
}
|
|
426
678
|
return;
|
|
427
679
|
}
|
|
428
680
|
case "yield": {
|
|
429
|
-
if (message.to === this.#instanceId)
|
|
430
|
-
void
|
|
681
|
+
if (message.to === this.#instanceId) {
|
|
682
|
+
this.#knownLeader = void 0;
|
|
683
|
+
this.#electInBackground("opfs election after yield", true);
|
|
684
|
+
}
|
|
431
685
|
return;
|
|
432
686
|
}
|
|
433
687
|
case "released": {
|
|
434
688
|
if (this.#knownLeader === message.leaderId)
|
|
435
689
|
this.#knownLeader = void 0;
|
|
690
|
+
this.#gracefulLeaders.add(message.leaderId);
|
|
691
|
+
if (this.#gracefulLeaders.size > 16) {
|
|
692
|
+
const [oldest] = this.#gracefulLeaders;
|
|
693
|
+
if (oldest !== void 0)
|
|
694
|
+
this.#gracefulLeaders.delete(oldest);
|
|
695
|
+
}
|
|
436
696
|
return;
|
|
437
697
|
}
|
|
438
698
|
}
|
|
439
699
|
}
|
|
700
|
+
#decline(message) {
|
|
701
|
+
this.#postOnce(message.from, {
|
|
702
|
+
kind: "declined",
|
|
703
|
+
requestId: message.requestId,
|
|
704
|
+
reason: "not-leader"
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
#armPendingTimer(pending, ms) {
|
|
708
|
+
clearTimeout(pending.timer);
|
|
709
|
+
pending.awaitingPing = false;
|
|
710
|
+
pending.timer = setTimeout(() => {
|
|
711
|
+
this.#onPendingTimeout(pending.message.requestId);
|
|
712
|
+
}, ms);
|
|
713
|
+
}
|
|
714
|
+
#onPendingTimeout(requestId) {
|
|
715
|
+
const pending = this.#pending.get(requestId);
|
|
716
|
+
if (pending === void 0)
|
|
717
|
+
return;
|
|
718
|
+
if (READ_METHODS.has(pending.message.method) || pending.awaitingPing || pending.patienceRounds >= MUTATION_PATIENCE_ROUNDS || this.#channel === void 0) {
|
|
719
|
+
this.#takePending(requestId);
|
|
720
|
+
pending.reject(RPC_TIMED_OUT);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
pending.patienceRounds += 1;
|
|
724
|
+
pending.awaitingPing = true;
|
|
725
|
+
this.#post({ kind: "ping" });
|
|
726
|
+
pending.timer = setTimeout(() => {
|
|
727
|
+
this.#onPendingTimeout(requestId);
|
|
728
|
+
}, DISCOVERY_WAIT_MS);
|
|
729
|
+
}
|
|
440
730
|
#takePending(requestId) {
|
|
441
731
|
const pending = this.#pending.get(requestId);
|
|
442
732
|
if (pending === void 0)
|
|
@@ -446,7 +736,7 @@ class OpfsBlockStore {
|
|
|
446
736
|
return pending;
|
|
447
737
|
}
|
|
448
738
|
async #serveOp(message) {
|
|
449
|
-
const requestKey =
|
|
739
|
+
const requestKey = servedRequestKey(message.from, message.requestId);
|
|
450
740
|
if (this.#inFlightMutations.has(requestKey) || this.#settledMutations.has(requestKey)) {
|
|
451
741
|
await this.#serveOpLocked(message, requestKey);
|
|
452
742
|
return;
|
|
@@ -476,10 +766,38 @@ class OpfsBlockStore {
|
|
|
476
766
|
let fingerprint;
|
|
477
767
|
try {
|
|
478
768
|
fingerprint = await fingerprintStoreRequest(message.method, message.args);
|
|
479
|
-
} catch {
|
|
769
|
+
} catch (error) {
|
|
770
|
+
this.#answer(message.from, {
|
|
771
|
+
kind: "result",
|
|
772
|
+
requestId: message.requestId,
|
|
773
|
+
ok: false,
|
|
774
|
+
error: serializeStoreError(error)
|
|
775
|
+
});
|
|
480
776
|
return;
|
|
481
777
|
}
|
|
482
|
-
|
|
778
|
+
let remembered = isRead ? void 0 : this.#settledMutations.get(requestKey);
|
|
779
|
+
const inFlight = isRead || remembered !== void 0 ? void 0 : this.#inFlightMutations.get(requestKey);
|
|
780
|
+
if (remembered === void 0 && inFlight === void 0 && !isRead) {
|
|
781
|
+
const logged = leader.servedOutcome(requestKey);
|
|
782
|
+
if (logged !== void 0) {
|
|
783
|
+
if (logged.method !== message.method || logged.signature !== fingerprint.signature) {
|
|
784
|
+
this.#rejectReusedRequestIdentity(message);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (!logged.settled || logged.withheld === true) {
|
|
788
|
+
this.#answer(message.from, { kind: "uncertain", requestId: message.requestId });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const outcome = { ok: true, value: logged.result };
|
|
792
|
+
this.#rememberSettledMutation(requestKey, logged.method, { signature: logged.signature, retainedBytes: logged.requestBytes }, outcome);
|
|
793
|
+
remembered = this.#settledMutations.get(requestKey) ?? {
|
|
794
|
+
method: logged.method,
|
|
795
|
+
signature: logged.signature,
|
|
796
|
+
requestBytes: logged.requestBytes,
|
|
797
|
+
outcome
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
}
|
|
483
801
|
let settled;
|
|
484
802
|
if (remembered !== void 0) {
|
|
485
803
|
if (!sameServedRequest(remembered, message, fingerprint.signature)) {
|
|
@@ -489,10 +807,7 @@ class OpfsBlockStore {
|
|
|
489
807
|
this.#settledMutations.delete(requestKey);
|
|
490
808
|
this.#settledMutations.set(requestKey, remembered);
|
|
491
809
|
settled = remembered.outcome;
|
|
492
|
-
} else if (
|
|
493
|
-
const inFlight = this.#inFlightMutations.get(requestKey);
|
|
494
|
-
if (inFlight === void 0)
|
|
495
|
-
throw new Error("In-flight RPC identity disappeared");
|
|
810
|
+
} else if (inFlight !== void 0) {
|
|
496
811
|
if (!sameServedRequest(inFlight, message, fingerprint.signature)) {
|
|
497
812
|
this.#rejectReusedRequestIdentity(message);
|
|
498
813
|
return;
|
|
@@ -500,6 +815,10 @@ class OpfsBlockStore {
|
|
|
500
815
|
this.#answer(message.from, { kind: "busy", requestId: message.requestId });
|
|
501
816
|
settled = await inFlight.outcome;
|
|
502
817
|
} else {
|
|
818
|
+
if (!isRead && message.sentAt < leader.servedCoverageSince) {
|
|
819
|
+
this.#answer(message.from, { kind: "uncertain", requestId: message.requestId });
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
503
822
|
if (!isRead && (this.#inFlightMutations.size >= RPC_IN_FLIGHT_LIMIT || this.#inFlightMutationBytes + fingerprint.retainedBytes > RPC_IN_FLIGHT_MUTATION_BYTES)) {
|
|
504
823
|
this.#answer(message.from, {
|
|
505
824
|
kind: "result",
|
|
@@ -521,11 +840,15 @@ class OpfsBlockStore {
|
|
|
521
840
|
this.#readCapacityChanged = { promise, resolve };
|
|
522
841
|
}
|
|
523
842
|
await this.#readCapacityChanged.promise;
|
|
524
|
-
if (this.#closed || this.#leader !== leader)
|
|
843
|
+
if (this.#closed || this.#leader !== leader) {
|
|
844
|
+
this.#decline(message);
|
|
525
845
|
return;
|
|
846
|
+
}
|
|
526
847
|
}
|
|
527
|
-
if (this.#closed || this.#leader !== leader)
|
|
848
|
+
if (this.#closed || this.#leader !== leader) {
|
|
849
|
+
this.#decline(message);
|
|
528
850
|
return;
|
|
851
|
+
}
|
|
529
852
|
this.#inFlightReadBytes += reservation;
|
|
530
853
|
try {
|
|
531
854
|
settled = await this.#executeServedOpAfterGate(leader, message, true);
|
|
@@ -535,11 +858,17 @@ class OpfsBlockStore {
|
|
|
535
858
|
}
|
|
536
859
|
} else {
|
|
537
860
|
this.#inFlightMutationBytes += fingerprint.retainedBytes;
|
|
538
|
-
const execution = this.#executeServedOpAfterGate(leader, message, false
|
|
861
|
+
const execution = this.#executeServedOpAfterGate(leader, message, false, {
|
|
862
|
+
key: requestKey,
|
|
863
|
+
method: message.method,
|
|
864
|
+
signature: fingerprint.signature,
|
|
865
|
+
requestBytes: fingerprint.retainedBytes,
|
|
866
|
+
sentAt: Math.min(message.sentAt, Date.now())
|
|
867
|
+
});
|
|
539
868
|
const outcome = execution.then((result) => {
|
|
540
869
|
this.#inFlightMutations.delete(requestKey);
|
|
541
870
|
this.#inFlightMutationBytes = Math.max(0, this.#inFlightMutationBytes - fingerprint.retainedBytes);
|
|
542
|
-
if (!this.#closed) {
|
|
871
|
+
if (!this.#closed && result !== DECLINED_OUTCOME) {
|
|
543
872
|
this.#rememberSettledMutation(requestKey, message.method, fingerprint, result);
|
|
544
873
|
}
|
|
545
874
|
return result;
|
|
@@ -553,6 +882,8 @@ class OpfsBlockStore {
|
|
|
553
882
|
settled = await outcome;
|
|
554
883
|
}
|
|
555
884
|
}
|
|
885
|
+
if (settled === DECLINED_OUTCOME)
|
|
886
|
+
return;
|
|
556
887
|
if (this.#dropNextRpcResultForTests) {
|
|
557
888
|
this.#dropNextRpcResultForTests = false;
|
|
558
889
|
return;
|
|
@@ -601,20 +932,58 @@ class OpfsBlockStore {
|
|
|
601
932
|
this.#readCapacityChanged = void 0;
|
|
602
933
|
waiting?.resolve();
|
|
603
934
|
}
|
|
604
|
-
async #executeServedOpAfterGate(leader, message, isRead) {
|
|
935
|
+
async #executeServedOpAfterGate(leader, message, isRead, request) {
|
|
605
936
|
const gate = this.#servedMutationGateForTests;
|
|
606
937
|
if (!isRead && gate !== void 0)
|
|
607
938
|
await gate;
|
|
608
|
-
return this.#executeServedOp(leader, message);
|
|
939
|
+
return this.#executeServedOp(leader, message, request);
|
|
609
940
|
}
|
|
610
|
-
|
|
941
|
+
#leads(leader) {
|
|
942
|
+
return this.#leader === leader;
|
|
943
|
+
}
|
|
944
|
+
#mutationTail = Promise.resolve();
|
|
945
|
+
#withMutationTurn(run) {
|
|
946
|
+
const turn = this.#mutationTail.then(run);
|
|
947
|
+
this.#mutationTail = turn.then(() => void 0, () => void 0);
|
|
948
|
+
return turn;
|
|
949
|
+
}
|
|
950
|
+
async #executeServedOp(leader, message, request) {
|
|
951
|
+
if (leader.isClosed() || !this.#leads(leader)) {
|
|
952
|
+
this.#decline(message);
|
|
953
|
+
return DECLINED_OUTCOME;
|
|
954
|
+
}
|
|
611
955
|
try {
|
|
612
956
|
const method = leader[message.method];
|
|
613
957
|
if (method === void 0) {
|
|
614
958
|
return { ok: false, error: { name: "Error", message: "Unknown store operation" } };
|
|
615
959
|
}
|
|
616
|
-
|
|
960
|
+
if (request === void 0) {
|
|
961
|
+
return { ok: true, value: await method.apply(leader, message.args) };
|
|
962
|
+
}
|
|
963
|
+
return await this.#withMutationTurn(async () => {
|
|
964
|
+
if (leader.isClosed() || !this.#leads(leader)) {
|
|
965
|
+
this.#decline(message);
|
|
966
|
+
return DECLINED_OUTCOME;
|
|
967
|
+
}
|
|
968
|
+
leader.servingRequest = request;
|
|
969
|
+
try {
|
|
970
|
+
const value = await method.apply(leader, message.args);
|
|
971
|
+
if (leader.servingRequest !== request) {
|
|
972
|
+
await leader.completeServed(request.key, value).catch((error) => {
|
|
973
|
+
this.#diagnostic(error, `opfs served result for ${message.method}`);
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
return { ok: true, value };
|
|
977
|
+
} finally {
|
|
978
|
+
if (leader.servingRequest === request)
|
|
979
|
+
leader.servingRequest = void 0;
|
|
980
|
+
}
|
|
981
|
+
});
|
|
617
982
|
} catch (error) {
|
|
983
|
+
if (leader.isClosed() && !this.#leads(leader)) {
|
|
984
|
+
this.#decline(message);
|
|
985
|
+
return DECLINED_OUTCOME;
|
|
986
|
+
}
|
|
618
987
|
return { ok: false, error: serializeStoreError(error) };
|
|
619
988
|
}
|
|
620
989
|
}
|
|
@@ -663,9 +1032,20 @@ class OpfsBlockStore {
|
|
|
663
1032
|
#closeChannels() {
|
|
664
1033
|
this.#channel?.close();
|
|
665
1034
|
this.#channel = void 0;
|
|
666
|
-
this.#inbox?.close();
|
|
667
|
-
this.#inbox = void 0;
|
|
668
1035
|
this.#closeAnswerChannels();
|
|
1036
|
+
const inbox = this.#inbox;
|
|
1037
|
+
this.#inbox = void 0;
|
|
1038
|
+
if (inbox === void 0)
|
|
1039
|
+
return;
|
|
1040
|
+
if (this.#inboxLingerTimer !== void 0)
|
|
1041
|
+
clearTimeout(this.#inboxLingerTimer);
|
|
1042
|
+
const timer = setTimeout(() => {
|
|
1043
|
+
this.#inboxLingerTimer = void 0;
|
|
1044
|
+
inbox.close();
|
|
1045
|
+
}, DECLINE_AFTER_CLOSE_MS);
|
|
1046
|
+
timer.unref?.();
|
|
1047
|
+
inbox.unref?.();
|
|
1048
|
+
this.#inboxLingerTimer = timer;
|
|
669
1049
|
}
|
|
670
1050
|
#assertOpen() {
|
|
671
1051
|
if (this.#closed)
|
|
@@ -674,14 +1054,46 @@ class OpfsBlockStore {
|
|
|
674
1054
|
async #dispatch(method, args) {
|
|
675
1055
|
this.#assertOpen();
|
|
676
1056
|
const requestId = crypto.randomUUID();
|
|
677
|
-
|
|
1057
|
+
const sentAt = Date.now();
|
|
1058
|
+
const isRead = READ_METHODS.has(method);
|
|
1059
|
+
let sentRemotely = false;
|
|
1060
|
+
let mayHaveRun = false;
|
|
1061
|
+
this.#lastActivityAt = sentAt;
|
|
1062
|
+
while (Date.now() - Math.max(sentAt, this.#waitHeardAt) < this.#dispatchBudgetMs) {
|
|
678
1063
|
this.#assertOpen();
|
|
679
1064
|
const leader = this.#leader;
|
|
680
1065
|
if (leader !== void 0) {
|
|
681
1066
|
const bound = leader[method];
|
|
682
1067
|
if (bound === void 0)
|
|
683
1068
|
throw new Error(`Unknown store operation: ${method}`);
|
|
684
|
-
|
|
1069
|
+
try {
|
|
1070
|
+
if (isRead)
|
|
1071
|
+
return await bound.apply(leader, args);
|
|
1072
|
+
if (sentRemotely) {
|
|
1073
|
+
const key = servedRequestKey(this.#instanceId, requestId);
|
|
1074
|
+
const logged = leader.servedOutcome(key);
|
|
1075
|
+
if (logged !== void 0) {
|
|
1076
|
+
if (!logged.settled || logged.withheld === true) {
|
|
1077
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
1078
|
+
}
|
|
1079
|
+
return logged.result;
|
|
1080
|
+
}
|
|
1081
|
+
if (sentAt < leader.servedCoverageSince)
|
|
1082
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
1083
|
+
}
|
|
1084
|
+
return await this.#withMutationTurn(() => {
|
|
1085
|
+
if (leader.isClosed() || !this.#leads(leader))
|
|
1086
|
+
throw new OpfsLeaderClosedError();
|
|
1087
|
+
return bound.apply(leader, args);
|
|
1088
|
+
});
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
if (!this.#leads(leader) && (isRead || error instanceof OpfsLeaderClosedError)) {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
throw error;
|
|
1094
|
+
} finally {
|
|
1095
|
+
this.#lastActivityAt = Date.now();
|
|
1096
|
+
}
|
|
685
1097
|
}
|
|
686
1098
|
if (this.#channel === void 0) {
|
|
687
1099
|
if (await this.#tryBecomeLeader())
|
|
@@ -699,23 +1111,26 @@ class OpfsBlockStore {
|
|
|
699
1111
|
}
|
|
700
1112
|
}
|
|
701
1113
|
try {
|
|
702
|
-
|
|
1114
|
+
sentRemotely = true;
|
|
1115
|
+
return await this.#rpc(requestId, method, args, sentAt);
|
|
703
1116
|
} catch (error) {
|
|
704
|
-
if (error === RPC_TIMED_OUT) {
|
|
1117
|
+
if (error === RPC_DECLINED || error === RPC_TIMED_OUT) {
|
|
1118
|
+
if (error === RPC_TIMED_OUT)
|
|
1119
|
+
mayHaveRun = true;
|
|
705
1120
|
this.#knownLeader = void 0;
|
|
706
|
-
if (!READ_METHODS.has(method))
|
|
707
|
-
throw new OpfsUncertainOutcomeError(method);
|
|
708
1121
|
continue;
|
|
709
1122
|
}
|
|
710
1123
|
throw error;
|
|
711
1124
|
}
|
|
712
1125
|
}
|
|
1126
|
+
if (!isRead && mayHaveRun)
|
|
1127
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
713
1128
|
throw new OpfsCoordinationError("leader-unavailable", method);
|
|
714
1129
|
}
|
|
715
1130
|
#leaderKnown() {
|
|
716
1131
|
return this.#knownLeader !== void 0;
|
|
717
1132
|
}
|
|
718
|
-
#rpc(requestId, method, args) {
|
|
1133
|
+
#rpc(requestId, method, args, sentAt) {
|
|
719
1134
|
let retainedBytes;
|
|
720
1135
|
try {
|
|
721
1136
|
retainedBytes = estimateRpcValueBytes(args);
|
|
@@ -726,10 +1141,16 @@ class OpfsBlockStore {
|
|
|
726
1141
|
return Promise.reject(new OpfsCoordinationError("follower-queue-full", method));
|
|
727
1142
|
}
|
|
728
1143
|
return new Promise((resolve, reject) => {
|
|
729
|
-
const message = {
|
|
1144
|
+
const message = {
|
|
1145
|
+
kind: "op",
|
|
1146
|
+
requestId,
|
|
1147
|
+
from: this.#instanceId,
|
|
1148
|
+
method,
|
|
1149
|
+
args,
|
|
1150
|
+
sentAt
|
|
1151
|
+
};
|
|
730
1152
|
const timer = setTimeout(() => {
|
|
731
|
-
|
|
732
|
-
expired?.reject(RPC_TIMED_OUT);
|
|
1153
|
+
this.#onPendingTimeout(requestId);
|
|
733
1154
|
}, this.#rpcTimeoutMs);
|
|
734
1155
|
const leaderId = this.#knownLeader;
|
|
735
1156
|
this.#pending.set(requestId, {
|
|
@@ -738,7 +1159,9 @@ class OpfsBlockStore {
|
|
|
738
1159
|
sentTo: leaderId,
|
|
739
1160
|
resolve,
|
|
740
1161
|
reject,
|
|
741
|
-
timer
|
|
1162
|
+
timer,
|
|
1163
|
+
patienceRounds: 0,
|
|
1164
|
+
awaitingPing: false
|
|
742
1165
|
});
|
|
743
1166
|
this.#pendingRpcBytes += retainedBytes;
|
|
744
1167
|
if (leaderId !== void 0)
|
|
@@ -777,8 +1200,7 @@ class OpfsBlockStore {
|
|
|
777
1200
|
if (this.#closed)
|
|
778
1201
|
return;
|
|
779
1202
|
this.#closed = true;
|
|
780
|
-
|
|
781
|
-
clearTimeout(this.#reacquireTimer);
|
|
1203
|
+
this.#clearCoordinationTimers();
|
|
782
1204
|
for (const pending of this.#pending.values()) {
|
|
783
1205
|
clearTimeout(pending.timer);
|
|
784
1206
|
pending.reject(new Error("This OPFS store connection is closed"));
|
|
@@ -797,18 +1219,37 @@ class OpfsBlockStore {
|
|
|
797
1219
|
const leader = this.#leader;
|
|
798
1220
|
this.#leader = void 0;
|
|
799
1221
|
if (leader !== void 0) {
|
|
800
|
-
void leader
|
|
1222
|
+
void this.#shutdownAfterMutations(leader).catch((error) => {
|
|
1223
|
+
this.#diagnostic(error, "opfs close shutdown");
|
|
801
1224
|
leader.crash();
|
|
802
|
-
}).then(() => {
|
|
1225
|
+
}).then(() => this.#servedDrained()).then(() => {
|
|
1226
|
+
this.#stopKeepalive();
|
|
803
1227
|
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
804
1228
|
this.#closeChannels();
|
|
805
1229
|
this.#releaseWhenHandlesClose();
|
|
806
1230
|
});
|
|
807
1231
|
return;
|
|
808
1232
|
}
|
|
1233
|
+
if (this.#yielding !== void 0) {
|
|
1234
|
+
void this.#yielding.then(() => this.#servedDrained()).then(() => {
|
|
1235
|
+
this.#stopKeepalive();
|
|
1236
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
1237
|
+
this.#closeChannels();
|
|
1238
|
+
this.#releaseWhenHandlesClose();
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
this.#stopKeepalive();
|
|
809
1243
|
this.#closeChannels();
|
|
810
1244
|
this.#releaseWhenHandlesClose();
|
|
811
1245
|
}
|
|
1246
|
+
#clearCoordinationTimers() {
|
|
1247
|
+
if (this.#reacquireTimer !== void 0)
|
|
1248
|
+
clearTimeout(this.#reacquireTimer);
|
|
1249
|
+
this.#reacquireTimer = void 0;
|
|
1250
|
+
this.#clearDeferredBid();
|
|
1251
|
+
this.#clearHiddenIdleTimer();
|
|
1252
|
+
}
|
|
812
1253
|
#releaseWhenHandlesClose() {
|
|
813
1254
|
const release = this.#releaseConnectionLock;
|
|
814
1255
|
this.#releaseConnectionLock = void 0;
|
|
@@ -846,6 +1287,9 @@ class OpfsBlockStore {
|
|
|
846
1287
|
release();
|
|
847
1288
|
};
|
|
848
1289
|
}
|
|
1290
|
+
_oldestPendingRequestIdForTests() {
|
|
1291
|
+
return this.#pending.keys().next().value;
|
|
1292
|
+
}
|
|
849
1293
|
_resendOldestPendingForTests() {
|
|
850
1294
|
const pending = this.#pending.values().next().value;
|
|
851
1295
|
if (pending?.sentTo !== void 0)
|
|
@@ -863,8 +1307,9 @@ class OpfsBlockStore {
|
|
|
863
1307
|
}
|
|
864
1308
|
_crashForTests() {
|
|
865
1309
|
this.#closed = true;
|
|
866
|
-
|
|
867
|
-
|
|
1310
|
+
this.#clearCoordinationTimers();
|
|
1311
|
+
this.#stopKeepalive();
|
|
1312
|
+
this.#served.clear();
|
|
868
1313
|
for (const pending of this.#pending.values())
|
|
869
1314
|
clearTimeout(pending.timer);
|
|
870
1315
|
this.#pending.clear();
|
|
@@ -880,7 +1325,11 @@ class OpfsBlockStore {
|
|
|
880
1325
|
this.#servedRequestBytes = 0;
|
|
881
1326
|
this.#leader?.crash();
|
|
882
1327
|
this.#leader = void 0;
|
|
883
|
-
this.#
|
|
1328
|
+
this.#channel?.close();
|
|
1329
|
+
this.#channel = void 0;
|
|
1330
|
+
this.#inbox?.close();
|
|
1331
|
+
this.#inbox = void 0;
|
|
1332
|
+
this.#closeAnswerChannels();
|
|
884
1333
|
this.#releaseWhenHandlesClose();
|
|
885
1334
|
}
|
|
886
1335
|
async #ensureFormatMarker() {
|
|
@@ -997,13 +1446,29 @@ async function holdConnectionLock(name) {
|
|
|
997
1446
|
})).catch(reject);
|
|
998
1447
|
});
|
|
999
1448
|
}
|
|
1449
|
+
function servedRequestKey(from, requestId) {
|
|
1450
|
+
return `${String(from.length)}:${from}${requestId}`;
|
|
1451
|
+
}
|
|
1000
1452
|
const RPC_TIMED_OUT = new Error("The leader did not answer in time");
|
|
1453
|
+
const RPC_DECLINED = new Error("The connection asked is not the leader");
|
|
1454
|
+
const DECLINED_OUTCOME = { ok: false };
|
|
1001
1455
|
async function resolveDatabaseRoot(options) {
|
|
1002
1456
|
const encodedName = encodeSegment(validateStorageDatabaseName(options.name));
|
|
1003
1457
|
const root = options.root ?? await navigator.storage.getDirectory();
|
|
1004
1458
|
const namespace = await root.getDirectoryHandle("minnowdb", { create: true });
|
|
1005
1459
|
return namespace.getDirectoryHandle(encodedName, { create: true });
|
|
1006
1460
|
}
|
|
1461
|
+
async function opfsDatabaseExists(options) {
|
|
1462
|
+
const encodedName = encodeSegment(validateStorageDatabaseName(options.name));
|
|
1463
|
+
try {
|
|
1464
|
+
const root = options.root ?? await navigator.storage.getDirectory();
|
|
1465
|
+
const namespace = await root.getDirectoryHandle("minnowdb");
|
|
1466
|
+
await namespace.getDirectoryHandle(encodedName);
|
|
1467
|
+
return true;
|
|
1468
|
+
} catch {
|
|
1469
|
+
return false;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1007
1472
|
function isLockContention(error) {
|
|
1008
1473
|
return isDomError(error, "NoModificationAllowedError") || isDomError(error, "InvalidStateError");
|
|
1009
1474
|
}
|
|
@@ -1012,5 +1477,6 @@ function sleep(ms) {
|
|
|
1012
1477
|
}
|
|
1013
1478
|
export {
|
|
1014
1479
|
OpfsBlockStore,
|
|
1015
|
-
deleteOpfsDatabase
|
|
1480
|
+
deleteOpfsDatabase,
|
|
1481
|
+
opfsDatabaseExists
|
|
1016
1482
|
};
|