@minnowdb/core 0.9.0 → 0.10.0
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 +40 -0
- package/dist/engine/auto-store.js +115 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +155 -40
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1085 -227
- 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 +17 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +118 -14
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +24 -2
- package/dist/storage/indexeddb.js +494 -207
- package/dist/storage/opfs/leader.js +201 -14
- package/dist/storage/opfs/rpc.js +23 -43
- package/dist/storage/opfs/store.d.ts +20 -0
- package/dist/storage/opfs/store.js +501 -70
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +1 -1
- package/dist/storage/types.d.ts +29 -8
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +12 -0
- package/dist/transactions/index.js +83 -23
- package/dist/worker-protocol/index.d.ts +50 -2
- package/dist/worker-protocol/index.js +106 -4
- package/package.json +7 -2
|
@@ -3,11 +3,15 @@ import { validateTempRunPage, validateTempRunPageIdentity } from "../toolkit/rec
|
|
|
3
3
|
import { OpfsTree, encodeSegment, isDomError } from "./files.js";
|
|
4
4
|
import { LOG_FORMAT_VERSION } from "../toolkit/wire.js";
|
|
5
5
|
import { OpfsLeader } from "./leader.js";
|
|
6
|
-
import { rehydrateStoreError, estimateRpcValueBytes, fingerprintStoreRequest, MAX_OPFS_RPC_MESSAGE_BYTES, parseStoreRpcMessage, serializeStoreError } from "./rpc.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
9
|
const DISPATCH_ATTEMPTS = 10;
|
|
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,13 @@ 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
|
+
#onDiagnostic;
|
|
179
189
|
#instanceId = crypto.randomUUID();
|
|
180
190
|
#channelName;
|
|
181
191
|
#channel;
|
|
@@ -201,12 +211,26 @@ class OpfsBlockStore {
|
|
|
201
211
|
#servedMutationGateForTests;
|
|
202
212
|
#dropNextRpcResultForTests = false;
|
|
203
213
|
#reacquireTimer;
|
|
214
|
+
#served = /* @__PURE__ */ new Map();
|
|
215
|
+
#keepaliveTimer;
|
|
216
|
+
#deferredBid;
|
|
217
|
+
#hiddenIdleTimer;
|
|
218
|
+
#inboxLingerTimer;
|
|
219
|
+
#lastActivityAt = Date.now();
|
|
220
|
+
#othersSeen = false;
|
|
221
|
+
#gracefulLeaders = /* @__PURE__ */ new Set();
|
|
204
222
|
constructor(tree, options) {
|
|
205
223
|
this.#tree = tree;
|
|
206
224
|
this.#durability = options.durability ?? "strict";
|
|
207
225
|
this.#checkpointEntries = options.checkpointEntries;
|
|
208
226
|
this.#cleanupLimitBytes = options.cleanupLimitBytes;
|
|
227
|
+
this.#servedLedgerAgeMs = options.servedLedgerAgeMs;
|
|
228
|
+
this.#servedLedgerResultBytes = options.servedLedgerResultBytes;
|
|
209
229
|
this.#rpcTimeoutMs = options.rpcTimeoutMs ?? RPC_TIMEOUT_MS;
|
|
230
|
+
this.#yieldCooldownMs = options.yieldCooldownMs ?? YIELD_COOLDOWN_MS;
|
|
231
|
+
this.#hiddenIdleReleaseMs = options.hiddenIdleReleaseMs ?? HIDDEN_IDLE_RELEASE_MS;
|
|
232
|
+
this.#handoverGraceMs = options.handoverGraceMs ?? HANDOVER_GRACE_MS;
|
|
233
|
+
this.#onDiagnostic = options.onDiagnostic;
|
|
210
234
|
this.#channelName = `minnowdb-store:${options.name}`;
|
|
211
235
|
this.liveQueryChannelName = `minnowdb-live:opfs:${options.name}`;
|
|
212
236
|
}
|
|
@@ -246,11 +270,26 @@ class OpfsBlockStore {
|
|
|
246
270
|
throw error;
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
|
-
|
|
273
|
+
#diagnostic(error, context) {
|
|
274
|
+
try {
|
|
275
|
+
this.#onDiagnostic?.(error, context);
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
#electInBackground(context, force = false) {
|
|
280
|
+
this.#tryBecomeLeader(force).catch((error) => {
|
|
281
|
+
this.#diagnostic(error, context);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async #tryBecomeLeader(force = false) {
|
|
250
285
|
if (this.#closed)
|
|
251
286
|
return false;
|
|
252
287
|
if (this.#leader !== void 0)
|
|
253
288
|
return true;
|
|
289
|
+
if (this.#yielding !== void 0)
|
|
290
|
+
return false;
|
|
291
|
+
if (!force && Date.now() - this.#lastYieldAt < this.#handoverGraceMs)
|
|
292
|
+
return false;
|
|
254
293
|
if (this.#electing !== void 0)
|
|
255
294
|
return this.#electing;
|
|
256
295
|
const election = this.#elect();
|
|
@@ -275,7 +314,7 @@ class OpfsBlockStore {
|
|
|
275
314
|
try {
|
|
276
315
|
slotA = await this.#openWithRetry(["checkpoint-a"]);
|
|
277
316
|
slotB = await this.#openWithRetry(["checkpoint-b"]);
|
|
278
|
-
this.#leader = await OpfsLeader.recover(this.#tree, this.#durability === "strict", { wal, slotA, slotB }, this.#checkpointEntries, this.#cleanupLimitBytes);
|
|
317
|
+
this.#leader = await OpfsLeader.recover(this.#tree, this.#durability === "strict", { wal, slotA, slotB }, this.#checkpointEntries, this.#cleanupLimitBytes, this.#onDiagnostic, this.#servedLedgerAgeMs, this.#servedLedgerResultBytes);
|
|
279
318
|
} catch (error) {
|
|
280
319
|
wal.close();
|
|
281
320
|
slotA?.close();
|
|
@@ -287,15 +326,102 @@ class OpfsBlockStore {
|
|
|
287
326
|
if (this.#closed) {
|
|
288
327
|
const leader = this.#leader;
|
|
289
328
|
this.#leader = void 0;
|
|
290
|
-
await leader.shutdown().catch(() => {
|
|
329
|
+
await leader.shutdown().catch((error) => {
|
|
330
|
+
this.#diagnostic(error, "opfs shutdown after close during election");
|
|
291
331
|
leader.crash();
|
|
292
332
|
});
|
|
293
333
|
return false;
|
|
294
334
|
}
|
|
295
335
|
this.#knownLeader = this.#instanceId;
|
|
336
|
+
this.#leader.onBeforeCheckpoint = (expectedMs) => {
|
|
337
|
+
this.#holdServed(expectedMs);
|
|
338
|
+
};
|
|
296
339
|
this.#post({ kind: "leader", leaderId: this.#instanceId });
|
|
340
|
+
this.#post({ kind: "state", leaderId: this.#instanceId, foreground: this.#foreground });
|
|
341
|
+
if (!this.#foreground)
|
|
342
|
+
this.#armHiddenIdleTimer();
|
|
297
343
|
return true;
|
|
298
344
|
}
|
|
345
|
+
#holdServed(expectedMs) {
|
|
346
|
+
if (this.#served.size === 0)
|
|
347
|
+
return;
|
|
348
|
+
const ms = Math.min(MAX_OPFS_RPC_HOLD_MS, expectedMs * 2 + this.#rpcTimeoutMs);
|
|
349
|
+
for (const [requestId, from] of this.#served)
|
|
350
|
+
this.#answer(from, { kind: "hold", requestId, ms });
|
|
351
|
+
}
|
|
352
|
+
#keepaliveSuppressedForTests = false;
|
|
353
|
+
_suppressKeepaliveForTests() {
|
|
354
|
+
this.#keepaliveSuppressedForTests = true;
|
|
355
|
+
this.#stopKeepalive();
|
|
356
|
+
}
|
|
357
|
+
#startKeepalive() {
|
|
358
|
+
if (this.#keepaliveTimer !== void 0 || this.#keepaliveSuppressedForTests)
|
|
359
|
+
return;
|
|
360
|
+
const timer = setInterval(() => {
|
|
361
|
+
if (this.#served.size === 0) {
|
|
362
|
+
clearInterval(timer);
|
|
363
|
+
if (this.#keepaliveTimer === timer)
|
|
364
|
+
this.#keepaliveTimer = void 0;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
for (const [requestId, from] of this.#served) {
|
|
368
|
+
this.#answer(from, { kind: "busy", requestId });
|
|
369
|
+
}
|
|
370
|
+
}, Math.max(1, Math.floor(this.#rpcTimeoutMs / 2)));
|
|
371
|
+
timer.unref?.();
|
|
372
|
+
this.#keepaliveTimer = timer;
|
|
373
|
+
}
|
|
374
|
+
#stopKeepalive() {
|
|
375
|
+
if (this.#keepaliveTimer === void 0)
|
|
376
|
+
return;
|
|
377
|
+
clearInterval(this.#keepaliveTimer);
|
|
378
|
+
this.#keepaliveTimer = void 0;
|
|
379
|
+
}
|
|
380
|
+
#armHiddenIdleTimer() {
|
|
381
|
+
this.#clearHiddenIdleTimer();
|
|
382
|
+
if (this.#foreground || this.#leader === void 0 || this.#closed)
|
|
383
|
+
return;
|
|
384
|
+
const elapsed = Date.now() - this.#lastActivityAt;
|
|
385
|
+
const timer = setTimeout(() => {
|
|
386
|
+
this.#hiddenIdleTimer = void 0;
|
|
387
|
+
if (this.#closed || this.#leader === void 0 || this.#foreground)
|
|
388
|
+
return;
|
|
389
|
+
if (this.#othersSeen && this.#served.size === 0 && this.#yielding === void 0 && Date.now() - this.#lastActivityAt >= this.#hiddenIdleReleaseMs) {
|
|
390
|
+
this.#demote().catch((error) => {
|
|
391
|
+
this.#diagnostic(error, "opfs idle release");
|
|
392
|
+
});
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.#armHiddenIdleTimer();
|
|
396
|
+
}, Math.max(0, this.#hiddenIdleReleaseMs - elapsed));
|
|
397
|
+
timer.unref?.();
|
|
398
|
+
this.#hiddenIdleTimer = timer;
|
|
399
|
+
}
|
|
400
|
+
#clearHiddenIdleTimer() {
|
|
401
|
+
if (this.#hiddenIdleTimer === void 0)
|
|
402
|
+
return;
|
|
403
|
+
clearTimeout(this.#hiddenIdleTimer);
|
|
404
|
+
this.#hiddenIdleTimer = void 0;
|
|
405
|
+
}
|
|
406
|
+
async #demote() {
|
|
407
|
+
const leader = this.#leader;
|
|
408
|
+
if (leader === void 0)
|
|
409
|
+
return;
|
|
410
|
+
this.#leader = void 0;
|
|
411
|
+
this.#knownLeader = void 0;
|
|
412
|
+
const shutdown = leader.shutdown().catch((error) => {
|
|
413
|
+
this.#diagnostic(error, "opfs idle release shutdown");
|
|
414
|
+
leader.crash();
|
|
415
|
+
});
|
|
416
|
+
this.#yielding = shutdown;
|
|
417
|
+
await shutdown;
|
|
418
|
+
if (this.#yielding === shutdown)
|
|
419
|
+
this.#yielding = void 0;
|
|
420
|
+
if (this.#closed)
|
|
421
|
+
return;
|
|
422
|
+
this.#closeAnswerChannels();
|
|
423
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
424
|
+
}
|
|
299
425
|
async #openWithRetry(path) {
|
|
300
426
|
for (let attempt = 0; ; attempt += 1) {
|
|
301
427
|
try {
|
|
@@ -310,19 +436,66 @@ class OpfsBlockStore {
|
|
|
310
436
|
}
|
|
311
437
|
}
|
|
312
438
|
setForeground(foreground) {
|
|
439
|
+
if (this.#closed)
|
|
440
|
+
return;
|
|
441
|
+
const changed = this.#foreground !== foreground;
|
|
313
442
|
this.#foreground = foreground;
|
|
314
|
-
if (
|
|
443
|
+
if (this.#leader !== void 0) {
|
|
444
|
+
if (foreground) {
|
|
445
|
+
this.#clearHiddenIdleTimer();
|
|
446
|
+
this.#clearDeferredBid();
|
|
447
|
+
} else {
|
|
448
|
+
this.#lastActivityAt = Date.now();
|
|
449
|
+
this.#armHiddenIdleTimer();
|
|
450
|
+
}
|
|
451
|
+
if (changed)
|
|
452
|
+
this.#post({ kind: "state", leaderId: this.#instanceId, foreground });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (foreground)
|
|
315
456
|
this.#post({ kind: "bid", bidderId: this.#instanceId, foreground: true });
|
|
457
|
+
}
|
|
458
|
+
#clearDeferredBid() {
|
|
459
|
+
if (this.#deferredBid === void 0)
|
|
460
|
+
return;
|
|
461
|
+
clearTimeout(this.#deferredBid.timer);
|
|
462
|
+
this.#deferredBid = void 0;
|
|
463
|
+
}
|
|
464
|
+
#considerBid(bidderId) {
|
|
465
|
+
if (this.#leader === void 0 || this.#foreground || this.#yielding !== void 0)
|
|
466
|
+
return;
|
|
467
|
+
const remaining = this.#yieldCooldownMs - (Date.now() - this.#lastYieldAt);
|
|
468
|
+
if (remaining <= 0) {
|
|
469
|
+
this.#clearDeferredBid();
|
|
470
|
+
this.#yieldLeadership(bidderId).catch((error) => {
|
|
471
|
+
this.#diagnostic(error, "opfs yield");
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (this.#deferredBid !== void 0) {
|
|
476
|
+
this.#deferredBid.bidderId = bidderId;
|
|
477
|
+
return;
|
|
316
478
|
}
|
|
479
|
+
const timer = setTimeout(() => {
|
|
480
|
+
const deferred = this.#deferredBid;
|
|
481
|
+
this.#deferredBid = void 0;
|
|
482
|
+
if (deferred !== void 0 && !this.#closed)
|
|
483
|
+
this.#considerBid(deferred.bidderId);
|
|
484
|
+
}, remaining);
|
|
485
|
+
timer.unref?.();
|
|
486
|
+
this.#deferredBid = { bidderId, timer };
|
|
317
487
|
}
|
|
318
488
|
async #yieldLeadership(to) {
|
|
319
489
|
const leader = this.#leader;
|
|
320
490
|
if (leader === void 0)
|
|
321
491
|
return;
|
|
492
|
+
this.#clearHiddenIdleTimer();
|
|
493
|
+
this.#clearDeferredBid();
|
|
322
494
|
this.#leader = void 0;
|
|
323
495
|
this.#knownLeader = void 0;
|
|
324
496
|
this.#lastYieldAt = Date.now();
|
|
325
|
-
const shutdown = leader.shutdown().catch(() => {
|
|
497
|
+
const shutdown = leader.shutdown().catch((error) => {
|
|
498
|
+
this.#diagnostic(error, "opfs yield shutdown");
|
|
326
499
|
leader.crash();
|
|
327
500
|
});
|
|
328
501
|
this.#yielding = shutdown;
|
|
@@ -333,22 +506,32 @@ class OpfsBlockStore {
|
|
|
333
506
|
return;
|
|
334
507
|
this.#closeAnswerChannels();
|
|
335
508
|
this.#post({ kind: "yield", to });
|
|
509
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
336
510
|
if (this.#reacquireTimer !== void 0)
|
|
337
511
|
clearTimeout(this.#reacquireTimer);
|
|
338
512
|
this.#reacquireTimer = setTimeout(() => {
|
|
339
|
-
if (this.#knownLeader === void 0 && !this.#closed)
|
|
340
|
-
|
|
341
|
-
|
|
513
|
+
if (this.#knownLeader === void 0 && !this.#closed) {
|
|
514
|
+
this.#electInBackground("opfs reacquire after yield", true);
|
|
515
|
+
}
|
|
516
|
+
}, this.#handoverGraceMs);
|
|
342
517
|
}
|
|
343
518
|
#yielding;
|
|
344
519
|
#onMessage(message) {
|
|
345
|
-
if (this.#closed)
|
|
520
|
+
if (this.#closed) {
|
|
521
|
+
if (message.kind === "op")
|
|
522
|
+
this.#decline(message);
|
|
346
523
|
return;
|
|
524
|
+
}
|
|
347
525
|
if (this.#coordinationPausedForTests)
|
|
348
526
|
return;
|
|
527
|
+
this.#othersSeen = true;
|
|
349
528
|
switch (message.kind) {
|
|
350
529
|
case "op": {
|
|
351
|
-
if (this.#leader
|
|
530
|
+
if (this.#leader === void 0) {
|
|
531
|
+
this.#decline(message);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
{
|
|
352
535
|
const requestBytes = estimateRpcValueBytes(message.args);
|
|
353
536
|
if (this.#servedRequestCount >= RPC_SERVER_ADMISSION_LIMIT || this.#servedRequestBytes + requestBytes > RPC_SERVER_ADMISSION_BYTES) {
|
|
354
537
|
this.#answer(message.from, {
|
|
@@ -361,13 +544,51 @@ class OpfsBlockStore {
|
|
|
361
544
|
}
|
|
362
545
|
this.#servedRequestCount += 1;
|
|
363
546
|
this.#servedRequestBytes += requestBytes;
|
|
364
|
-
|
|
547
|
+
this.#lastActivityAt = Date.now();
|
|
548
|
+
const alreadyServing = this.#served.has(message.requestId);
|
|
549
|
+
this.#served.set(message.requestId, message.from);
|
|
550
|
+
this.#startKeepalive();
|
|
551
|
+
void this.#serveOp(message).catch((error) => {
|
|
552
|
+
this.#diagnostic(error, `opfs served ${message.method}`);
|
|
553
|
+
this.#answer(message.from, {
|
|
554
|
+
kind: "result",
|
|
555
|
+
requestId: message.requestId,
|
|
556
|
+
ok: false,
|
|
557
|
+
error: serializeStoreError(error)
|
|
558
|
+
});
|
|
559
|
+
}).finally(() => {
|
|
365
560
|
this.#servedRequestCount = Math.max(0, this.#servedRequestCount - 1);
|
|
366
561
|
this.#servedRequestBytes = Math.max(0, this.#servedRequestBytes - requestBytes);
|
|
562
|
+
if (!alreadyServing)
|
|
563
|
+
this.#served.delete(message.requestId);
|
|
564
|
+
this.#lastActivityAt = Date.now();
|
|
367
565
|
});
|
|
368
566
|
}
|
|
369
567
|
return;
|
|
370
568
|
}
|
|
569
|
+
case "declined": {
|
|
570
|
+
const pending = this.#takePending(message.requestId);
|
|
571
|
+
if (pending === void 0)
|
|
572
|
+
return;
|
|
573
|
+
clearTimeout(pending.timer);
|
|
574
|
+
pending.reject(RPC_DECLINED);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
case "uncertain": {
|
|
578
|
+
const pending = this.#takePending(message.requestId);
|
|
579
|
+
if (pending === void 0)
|
|
580
|
+
return;
|
|
581
|
+
clearTimeout(pending.timer);
|
|
582
|
+
pending.reject(new OpfsUncertainOutcomeError(pending.message.method));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
case "hold": {
|
|
586
|
+
const pending = this.#pending.get(message.requestId);
|
|
587
|
+
if (pending === void 0)
|
|
588
|
+
return;
|
|
589
|
+
this.#armPendingTimer(pending, Math.max(message.ms, this.#rpcTimeoutMs));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
371
592
|
case "result": {
|
|
372
593
|
const pending = this.#takePending(message.requestId);
|
|
373
594
|
if (pending === void 0)
|
|
@@ -383,26 +604,28 @@ class OpfsBlockStore {
|
|
|
383
604
|
const pending = this.#pending.get(message.requestId);
|
|
384
605
|
if (pending === void 0)
|
|
385
606
|
return;
|
|
386
|
-
|
|
387
|
-
pending.timer = setTimeout(() => {
|
|
388
|
-
const expired = this.#takePending(message.requestId);
|
|
389
|
-
expired?.reject(RPC_TIMED_OUT);
|
|
390
|
-
}, this.#rpcTimeoutMs);
|
|
607
|
+
this.#armPendingTimer(pending, this.#rpcTimeoutMs);
|
|
391
608
|
return;
|
|
392
609
|
}
|
|
393
610
|
case "leader": {
|
|
394
611
|
this.#knownLeader = message.leaderId;
|
|
612
|
+
this.#gracefulLeaders.delete(message.leaderId);
|
|
395
613
|
if (this.#reacquireTimer !== void 0) {
|
|
396
614
|
clearTimeout(this.#reacquireTimer);
|
|
397
615
|
this.#reacquireTimer = void 0;
|
|
398
616
|
}
|
|
399
|
-
for (const
|
|
400
|
-
if (pending.sentTo === message.leaderId)
|
|
617
|
+
for (const pending of this.#pending.values()) {
|
|
618
|
+
if (pending.sentTo === message.leaderId) {
|
|
619
|
+
if (pending.awaitingPing)
|
|
620
|
+
this.#armPendingTimer(pending, this.#rpcTimeoutMs);
|
|
401
621
|
continue;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
622
|
+
}
|
|
623
|
+
if (pending.sentTo === void 0) {
|
|
624
|
+
pending.sentTo = message.leaderId;
|
|
625
|
+
this.#send(message.leaderId, pending.message);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (!READ_METHODS.has(pending.message.method) && this.#gracefulLeaders.has(pending.sentTo)) {
|
|
406
629
|
continue;
|
|
407
630
|
}
|
|
408
631
|
pending.sentTo = message.leaderId;
|
|
@@ -413,6 +636,12 @@ class OpfsBlockStore {
|
|
|
413
636
|
}
|
|
414
637
|
return;
|
|
415
638
|
}
|
|
639
|
+
case "state": {
|
|
640
|
+
if (!message.foreground && this.#foreground && this.#leader === void 0 && message.leaderId !== this.#instanceId) {
|
|
641
|
+
this.#post({ kind: "bid", bidderId: this.#instanceId, foreground: true });
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
416
645
|
case "ping": {
|
|
417
646
|
if (this.#leader !== void 0) {
|
|
418
647
|
this.#post({ kind: "leader", leaderId: this.#instanceId });
|
|
@@ -420,23 +649,61 @@ class OpfsBlockStore {
|
|
|
420
649
|
return;
|
|
421
650
|
}
|
|
422
651
|
case "bid": {
|
|
423
|
-
if (
|
|
424
|
-
|
|
652
|
+
if (message.foreground && message.bidderId !== this.#instanceId) {
|
|
653
|
+
this.#considerBid(message.bidderId);
|
|
425
654
|
}
|
|
426
655
|
return;
|
|
427
656
|
}
|
|
428
657
|
case "yield": {
|
|
429
|
-
if (message.to === this.#instanceId)
|
|
430
|
-
void
|
|
658
|
+
if (message.to === this.#instanceId) {
|
|
659
|
+
this.#knownLeader = void 0;
|
|
660
|
+
this.#electInBackground("opfs election after yield", true);
|
|
661
|
+
}
|
|
431
662
|
return;
|
|
432
663
|
}
|
|
433
664
|
case "released": {
|
|
434
665
|
if (this.#knownLeader === message.leaderId)
|
|
435
666
|
this.#knownLeader = void 0;
|
|
667
|
+
this.#gracefulLeaders.add(message.leaderId);
|
|
668
|
+
if (this.#gracefulLeaders.size > 16) {
|
|
669
|
+
const [oldest] = this.#gracefulLeaders;
|
|
670
|
+
if (oldest !== void 0)
|
|
671
|
+
this.#gracefulLeaders.delete(oldest);
|
|
672
|
+
}
|
|
436
673
|
return;
|
|
437
674
|
}
|
|
438
675
|
}
|
|
439
676
|
}
|
|
677
|
+
#decline(message) {
|
|
678
|
+
this.#postOnce(message.from, {
|
|
679
|
+
kind: "declined",
|
|
680
|
+
requestId: message.requestId,
|
|
681
|
+
reason: "not-leader"
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
#armPendingTimer(pending, ms) {
|
|
685
|
+
clearTimeout(pending.timer);
|
|
686
|
+
pending.awaitingPing = false;
|
|
687
|
+
pending.timer = setTimeout(() => {
|
|
688
|
+
this.#onPendingTimeout(pending.message.requestId);
|
|
689
|
+
}, ms);
|
|
690
|
+
}
|
|
691
|
+
#onPendingTimeout(requestId) {
|
|
692
|
+
const pending = this.#pending.get(requestId);
|
|
693
|
+
if (pending === void 0)
|
|
694
|
+
return;
|
|
695
|
+
if (READ_METHODS.has(pending.message.method) || pending.awaitingPing || pending.patienceRounds >= MUTATION_PATIENCE_ROUNDS || this.#channel === void 0) {
|
|
696
|
+
this.#takePending(requestId);
|
|
697
|
+
pending.reject(RPC_TIMED_OUT);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
pending.patienceRounds += 1;
|
|
701
|
+
pending.awaitingPing = true;
|
|
702
|
+
this.#post({ kind: "ping" });
|
|
703
|
+
pending.timer = setTimeout(() => {
|
|
704
|
+
this.#onPendingTimeout(requestId);
|
|
705
|
+
}, DISCOVERY_WAIT_MS);
|
|
706
|
+
}
|
|
440
707
|
#takePending(requestId) {
|
|
441
708
|
const pending = this.#pending.get(requestId);
|
|
442
709
|
if (pending === void 0)
|
|
@@ -446,7 +713,7 @@ class OpfsBlockStore {
|
|
|
446
713
|
return pending;
|
|
447
714
|
}
|
|
448
715
|
async #serveOp(message) {
|
|
449
|
-
const requestKey =
|
|
716
|
+
const requestKey = servedRequestKey(message.from, message.requestId);
|
|
450
717
|
if (this.#inFlightMutations.has(requestKey) || this.#settledMutations.has(requestKey)) {
|
|
451
718
|
await this.#serveOpLocked(message, requestKey);
|
|
452
719
|
return;
|
|
@@ -476,10 +743,38 @@ class OpfsBlockStore {
|
|
|
476
743
|
let fingerprint;
|
|
477
744
|
try {
|
|
478
745
|
fingerprint = await fingerprintStoreRequest(message.method, message.args);
|
|
479
|
-
} catch {
|
|
746
|
+
} catch (error) {
|
|
747
|
+
this.#answer(message.from, {
|
|
748
|
+
kind: "result",
|
|
749
|
+
requestId: message.requestId,
|
|
750
|
+
ok: false,
|
|
751
|
+
error: serializeStoreError(error)
|
|
752
|
+
});
|
|
480
753
|
return;
|
|
481
754
|
}
|
|
482
|
-
|
|
755
|
+
let remembered = isRead ? void 0 : this.#settledMutations.get(requestKey);
|
|
756
|
+
const inFlight = isRead || remembered !== void 0 ? void 0 : this.#inFlightMutations.get(requestKey);
|
|
757
|
+
if (remembered === void 0 && inFlight === void 0 && !isRead) {
|
|
758
|
+
const logged = leader.servedOutcome(requestKey);
|
|
759
|
+
if (logged !== void 0) {
|
|
760
|
+
if (logged.method !== message.method || logged.signature !== fingerprint.signature) {
|
|
761
|
+
this.#rejectReusedRequestIdentity(message);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (!logged.settled || logged.withheld === true) {
|
|
765
|
+
this.#answer(message.from, { kind: "uncertain", requestId: message.requestId });
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const outcome = { ok: true, value: logged.result };
|
|
769
|
+
this.#rememberSettledMutation(requestKey, logged.method, { signature: logged.signature, retainedBytes: logged.requestBytes }, outcome);
|
|
770
|
+
remembered = this.#settledMutations.get(requestKey) ?? {
|
|
771
|
+
method: logged.method,
|
|
772
|
+
signature: logged.signature,
|
|
773
|
+
requestBytes: logged.requestBytes,
|
|
774
|
+
outcome
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
483
778
|
let settled;
|
|
484
779
|
if (remembered !== void 0) {
|
|
485
780
|
if (!sameServedRequest(remembered, message, fingerprint.signature)) {
|
|
@@ -489,10 +784,7 @@ class OpfsBlockStore {
|
|
|
489
784
|
this.#settledMutations.delete(requestKey);
|
|
490
785
|
this.#settledMutations.set(requestKey, remembered);
|
|
491
786
|
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");
|
|
787
|
+
} else if (inFlight !== void 0) {
|
|
496
788
|
if (!sameServedRequest(inFlight, message, fingerprint.signature)) {
|
|
497
789
|
this.#rejectReusedRequestIdentity(message);
|
|
498
790
|
return;
|
|
@@ -500,6 +792,10 @@ class OpfsBlockStore {
|
|
|
500
792
|
this.#answer(message.from, { kind: "busy", requestId: message.requestId });
|
|
501
793
|
settled = await inFlight.outcome;
|
|
502
794
|
} else {
|
|
795
|
+
if (!isRead && message.sentAt < leader.servedCoverageSince) {
|
|
796
|
+
this.#answer(message.from, { kind: "uncertain", requestId: message.requestId });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
503
799
|
if (!isRead && (this.#inFlightMutations.size >= RPC_IN_FLIGHT_LIMIT || this.#inFlightMutationBytes + fingerprint.retainedBytes > RPC_IN_FLIGHT_MUTATION_BYTES)) {
|
|
504
800
|
this.#answer(message.from, {
|
|
505
801
|
kind: "result",
|
|
@@ -521,11 +817,15 @@ class OpfsBlockStore {
|
|
|
521
817
|
this.#readCapacityChanged = { promise, resolve };
|
|
522
818
|
}
|
|
523
819
|
await this.#readCapacityChanged.promise;
|
|
524
|
-
if (this.#closed || this.#leader !== leader)
|
|
820
|
+
if (this.#closed || this.#leader !== leader) {
|
|
821
|
+
this.#decline(message);
|
|
525
822
|
return;
|
|
823
|
+
}
|
|
526
824
|
}
|
|
527
|
-
if (this.#closed || this.#leader !== leader)
|
|
825
|
+
if (this.#closed || this.#leader !== leader) {
|
|
826
|
+
this.#decline(message);
|
|
528
827
|
return;
|
|
828
|
+
}
|
|
529
829
|
this.#inFlightReadBytes += reservation;
|
|
530
830
|
try {
|
|
531
831
|
settled = await this.#executeServedOpAfterGate(leader, message, true);
|
|
@@ -535,11 +835,17 @@ class OpfsBlockStore {
|
|
|
535
835
|
}
|
|
536
836
|
} else {
|
|
537
837
|
this.#inFlightMutationBytes += fingerprint.retainedBytes;
|
|
538
|
-
const execution = this.#executeServedOpAfterGate(leader, message, false
|
|
838
|
+
const execution = this.#executeServedOpAfterGate(leader, message, false, {
|
|
839
|
+
key: requestKey,
|
|
840
|
+
method: message.method,
|
|
841
|
+
signature: fingerprint.signature,
|
|
842
|
+
requestBytes: fingerprint.retainedBytes,
|
|
843
|
+
sentAt: Math.min(message.sentAt, Date.now())
|
|
844
|
+
});
|
|
539
845
|
const outcome = execution.then((result) => {
|
|
540
846
|
this.#inFlightMutations.delete(requestKey);
|
|
541
847
|
this.#inFlightMutationBytes = Math.max(0, this.#inFlightMutationBytes - fingerprint.retainedBytes);
|
|
542
|
-
if (!this.#closed) {
|
|
848
|
+
if (!this.#closed && result !== DECLINED_OUTCOME) {
|
|
543
849
|
this.#rememberSettledMutation(requestKey, message.method, fingerprint, result);
|
|
544
850
|
}
|
|
545
851
|
return result;
|
|
@@ -553,6 +859,8 @@ class OpfsBlockStore {
|
|
|
553
859
|
settled = await outcome;
|
|
554
860
|
}
|
|
555
861
|
}
|
|
862
|
+
if (settled === DECLINED_OUTCOME)
|
|
863
|
+
return;
|
|
556
864
|
if (this.#dropNextRpcResultForTests) {
|
|
557
865
|
this.#dropNextRpcResultForTests = false;
|
|
558
866
|
return;
|
|
@@ -601,20 +909,58 @@ class OpfsBlockStore {
|
|
|
601
909
|
this.#readCapacityChanged = void 0;
|
|
602
910
|
waiting?.resolve();
|
|
603
911
|
}
|
|
604
|
-
async #executeServedOpAfterGate(leader, message, isRead) {
|
|
912
|
+
async #executeServedOpAfterGate(leader, message, isRead, request) {
|
|
605
913
|
const gate = this.#servedMutationGateForTests;
|
|
606
914
|
if (!isRead && gate !== void 0)
|
|
607
915
|
await gate;
|
|
608
|
-
return this.#executeServedOp(leader, message);
|
|
916
|
+
return this.#executeServedOp(leader, message, request);
|
|
917
|
+
}
|
|
918
|
+
#leads(leader) {
|
|
919
|
+
return this.#leader === leader;
|
|
920
|
+
}
|
|
921
|
+
#mutationTail = Promise.resolve();
|
|
922
|
+
#withMutationTurn(run) {
|
|
923
|
+
const turn = this.#mutationTail.then(run);
|
|
924
|
+
this.#mutationTail = turn.then(() => void 0, () => void 0);
|
|
925
|
+
return turn;
|
|
609
926
|
}
|
|
610
|
-
async #executeServedOp(leader, message) {
|
|
927
|
+
async #executeServedOp(leader, message, request) {
|
|
928
|
+
if (leader.isClosed() || !this.#leads(leader)) {
|
|
929
|
+
this.#decline(message);
|
|
930
|
+
return DECLINED_OUTCOME;
|
|
931
|
+
}
|
|
611
932
|
try {
|
|
612
933
|
const method = leader[message.method];
|
|
613
934
|
if (method === void 0) {
|
|
614
935
|
return { ok: false, error: { name: "Error", message: "Unknown store operation" } };
|
|
615
936
|
}
|
|
616
|
-
|
|
937
|
+
if (request === void 0) {
|
|
938
|
+
return { ok: true, value: await method.apply(leader, message.args) };
|
|
939
|
+
}
|
|
940
|
+
return await this.#withMutationTurn(async () => {
|
|
941
|
+
if (leader.isClosed() || !this.#leads(leader)) {
|
|
942
|
+
this.#decline(message);
|
|
943
|
+
return DECLINED_OUTCOME;
|
|
944
|
+
}
|
|
945
|
+
leader.servingRequest = request;
|
|
946
|
+
try {
|
|
947
|
+
const value = await method.apply(leader, message.args);
|
|
948
|
+
if (leader.servingRequest !== request) {
|
|
949
|
+
await leader.completeServed(request.key, value).catch((error) => {
|
|
950
|
+
this.#diagnostic(error, `opfs served result for ${message.method}`);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return { ok: true, value };
|
|
954
|
+
} finally {
|
|
955
|
+
if (leader.servingRequest === request)
|
|
956
|
+
leader.servingRequest = void 0;
|
|
957
|
+
}
|
|
958
|
+
});
|
|
617
959
|
} catch (error) {
|
|
960
|
+
if (leader.isClosed() && !this.#leads(leader)) {
|
|
961
|
+
this.#decline(message);
|
|
962
|
+
return DECLINED_OUTCOME;
|
|
963
|
+
}
|
|
618
964
|
return { ok: false, error: serializeStoreError(error) };
|
|
619
965
|
}
|
|
620
966
|
}
|
|
@@ -663,9 +1009,20 @@ class OpfsBlockStore {
|
|
|
663
1009
|
#closeChannels() {
|
|
664
1010
|
this.#channel?.close();
|
|
665
1011
|
this.#channel = void 0;
|
|
666
|
-
this.#inbox?.close();
|
|
667
|
-
this.#inbox = void 0;
|
|
668
1012
|
this.#closeAnswerChannels();
|
|
1013
|
+
const inbox = this.#inbox;
|
|
1014
|
+
this.#inbox = void 0;
|
|
1015
|
+
if (inbox === void 0)
|
|
1016
|
+
return;
|
|
1017
|
+
if (this.#inboxLingerTimer !== void 0)
|
|
1018
|
+
clearTimeout(this.#inboxLingerTimer);
|
|
1019
|
+
const timer = setTimeout(() => {
|
|
1020
|
+
this.#inboxLingerTimer = void 0;
|
|
1021
|
+
inbox.close();
|
|
1022
|
+
}, DECLINE_AFTER_CLOSE_MS);
|
|
1023
|
+
timer.unref?.();
|
|
1024
|
+
inbox.unref?.();
|
|
1025
|
+
this.#inboxLingerTimer = timer;
|
|
669
1026
|
}
|
|
670
1027
|
#assertOpen() {
|
|
671
1028
|
if (this.#closed)
|
|
@@ -674,6 +1031,11 @@ class OpfsBlockStore {
|
|
|
674
1031
|
async #dispatch(method, args) {
|
|
675
1032
|
this.#assertOpen();
|
|
676
1033
|
const requestId = crypto.randomUUID();
|
|
1034
|
+
const sentAt = Date.now();
|
|
1035
|
+
const isRead = READ_METHODS.has(method);
|
|
1036
|
+
let sentRemotely = false;
|
|
1037
|
+
let mayHaveRun = false;
|
|
1038
|
+
this.#lastActivityAt = sentAt;
|
|
677
1039
|
for (let attempt = 0; attempt < DISPATCH_ATTEMPTS; attempt += 1) {
|
|
678
1040
|
this.#assertOpen();
|
|
679
1041
|
const leader = this.#leader;
|
|
@@ -681,7 +1043,25 @@ class OpfsBlockStore {
|
|
|
681
1043
|
const bound = leader[method];
|
|
682
1044
|
if (bound === void 0)
|
|
683
1045
|
throw new Error(`Unknown store operation: ${method}`);
|
|
684
|
-
|
|
1046
|
+
try {
|
|
1047
|
+
if (isRead)
|
|
1048
|
+
return await bound.apply(leader, args);
|
|
1049
|
+
if (sentRemotely) {
|
|
1050
|
+
const key = servedRequestKey(this.#instanceId, requestId);
|
|
1051
|
+
const logged = leader.servedOutcome(key);
|
|
1052
|
+
if (logged !== void 0) {
|
|
1053
|
+
if (!logged.settled || logged.withheld === true) {
|
|
1054
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
1055
|
+
}
|
|
1056
|
+
return logged.result;
|
|
1057
|
+
}
|
|
1058
|
+
if (sentAt < leader.servedCoverageSince)
|
|
1059
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
1060
|
+
}
|
|
1061
|
+
return await this.#withMutationTurn(() => bound.apply(leader, args));
|
|
1062
|
+
} finally {
|
|
1063
|
+
this.#lastActivityAt = Date.now();
|
|
1064
|
+
}
|
|
685
1065
|
}
|
|
686
1066
|
if (this.#channel === void 0) {
|
|
687
1067
|
if (await this.#tryBecomeLeader())
|
|
@@ -699,23 +1079,26 @@ class OpfsBlockStore {
|
|
|
699
1079
|
}
|
|
700
1080
|
}
|
|
701
1081
|
try {
|
|
702
|
-
|
|
1082
|
+
sentRemotely = true;
|
|
1083
|
+
return await this.#rpc(requestId, method, args, sentAt);
|
|
703
1084
|
} catch (error) {
|
|
704
|
-
if (error === RPC_TIMED_OUT) {
|
|
1085
|
+
if (error === RPC_DECLINED || error === RPC_TIMED_OUT) {
|
|
1086
|
+
if (error === RPC_TIMED_OUT)
|
|
1087
|
+
mayHaveRun = true;
|
|
705
1088
|
this.#knownLeader = void 0;
|
|
706
|
-
if (!READ_METHODS.has(method))
|
|
707
|
-
throw new OpfsUncertainOutcomeError(method);
|
|
708
1089
|
continue;
|
|
709
1090
|
}
|
|
710
1091
|
throw error;
|
|
711
1092
|
}
|
|
712
1093
|
}
|
|
1094
|
+
if (!isRead && mayHaveRun)
|
|
1095
|
+
throw new OpfsUncertainOutcomeError(method);
|
|
713
1096
|
throw new OpfsCoordinationError("leader-unavailable", method);
|
|
714
1097
|
}
|
|
715
1098
|
#leaderKnown() {
|
|
716
1099
|
return this.#knownLeader !== void 0;
|
|
717
1100
|
}
|
|
718
|
-
#rpc(requestId, method, args) {
|
|
1101
|
+
#rpc(requestId, method, args, sentAt) {
|
|
719
1102
|
let retainedBytes;
|
|
720
1103
|
try {
|
|
721
1104
|
retainedBytes = estimateRpcValueBytes(args);
|
|
@@ -726,10 +1109,16 @@ class OpfsBlockStore {
|
|
|
726
1109
|
return Promise.reject(new OpfsCoordinationError("follower-queue-full", method));
|
|
727
1110
|
}
|
|
728
1111
|
return new Promise((resolve, reject) => {
|
|
729
|
-
const message = {
|
|
1112
|
+
const message = {
|
|
1113
|
+
kind: "op",
|
|
1114
|
+
requestId,
|
|
1115
|
+
from: this.#instanceId,
|
|
1116
|
+
method,
|
|
1117
|
+
args,
|
|
1118
|
+
sentAt
|
|
1119
|
+
};
|
|
730
1120
|
const timer = setTimeout(() => {
|
|
731
|
-
|
|
732
|
-
expired?.reject(RPC_TIMED_OUT);
|
|
1121
|
+
this.#onPendingTimeout(requestId);
|
|
733
1122
|
}, this.#rpcTimeoutMs);
|
|
734
1123
|
const leaderId = this.#knownLeader;
|
|
735
1124
|
this.#pending.set(requestId, {
|
|
@@ -738,7 +1127,9 @@ class OpfsBlockStore {
|
|
|
738
1127
|
sentTo: leaderId,
|
|
739
1128
|
resolve,
|
|
740
1129
|
reject,
|
|
741
|
-
timer
|
|
1130
|
+
timer,
|
|
1131
|
+
patienceRounds: 0,
|
|
1132
|
+
awaitingPing: false
|
|
742
1133
|
});
|
|
743
1134
|
this.#pendingRpcBytes += retainedBytes;
|
|
744
1135
|
if (leaderId !== void 0)
|
|
@@ -777,8 +1168,7 @@ class OpfsBlockStore {
|
|
|
777
1168
|
if (this.#closed)
|
|
778
1169
|
return;
|
|
779
1170
|
this.#closed = true;
|
|
780
|
-
|
|
781
|
-
clearTimeout(this.#reacquireTimer);
|
|
1171
|
+
this.#clearCoordinationTimers();
|
|
782
1172
|
for (const pending of this.#pending.values()) {
|
|
783
1173
|
clearTimeout(pending.timer);
|
|
784
1174
|
pending.reject(new Error("This OPFS store connection is closed"));
|
|
@@ -797,7 +1187,8 @@ class OpfsBlockStore {
|
|
|
797
1187
|
const leader = this.#leader;
|
|
798
1188
|
this.#leader = void 0;
|
|
799
1189
|
if (leader !== void 0) {
|
|
800
|
-
void leader.shutdown().catch(() => {
|
|
1190
|
+
void leader.shutdown().catch((error) => {
|
|
1191
|
+
this.#diagnostic(error, "opfs close shutdown");
|
|
801
1192
|
leader.crash();
|
|
802
1193
|
}).then(() => {
|
|
803
1194
|
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
@@ -806,9 +1197,26 @@ class OpfsBlockStore {
|
|
|
806
1197
|
});
|
|
807
1198
|
return;
|
|
808
1199
|
}
|
|
1200
|
+
if (this.#yielding !== void 0) {
|
|
1201
|
+
void this.#yielding.then(() => {
|
|
1202
|
+
this.#post({ kind: "released", leaderId: this.#instanceId });
|
|
1203
|
+
this.#closeChannels();
|
|
1204
|
+
this.#releaseWhenHandlesClose();
|
|
1205
|
+
});
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
809
1208
|
this.#closeChannels();
|
|
810
1209
|
this.#releaseWhenHandlesClose();
|
|
811
1210
|
}
|
|
1211
|
+
#clearCoordinationTimers() {
|
|
1212
|
+
if (this.#reacquireTimer !== void 0)
|
|
1213
|
+
clearTimeout(this.#reacquireTimer);
|
|
1214
|
+
this.#reacquireTimer = void 0;
|
|
1215
|
+
this.#stopKeepalive();
|
|
1216
|
+
this.#clearDeferredBid();
|
|
1217
|
+
this.#clearHiddenIdleTimer();
|
|
1218
|
+
this.#served.clear();
|
|
1219
|
+
}
|
|
812
1220
|
#releaseWhenHandlesClose() {
|
|
813
1221
|
const release = this.#releaseConnectionLock;
|
|
814
1222
|
this.#releaseConnectionLock = void 0;
|
|
@@ -846,6 +1254,9 @@ class OpfsBlockStore {
|
|
|
846
1254
|
release();
|
|
847
1255
|
};
|
|
848
1256
|
}
|
|
1257
|
+
_oldestPendingRequestIdForTests() {
|
|
1258
|
+
return this.#pending.keys().next().value;
|
|
1259
|
+
}
|
|
849
1260
|
_resendOldestPendingForTests() {
|
|
850
1261
|
const pending = this.#pending.values().next().value;
|
|
851
1262
|
if (pending?.sentTo !== void 0)
|
|
@@ -863,8 +1274,7 @@ class OpfsBlockStore {
|
|
|
863
1274
|
}
|
|
864
1275
|
_crashForTests() {
|
|
865
1276
|
this.#closed = true;
|
|
866
|
-
|
|
867
|
-
clearTimeout(this.#reacquireTimer);
|
|
1277
|
+
this.#clearCoordinationTimers();
|
|
868
1278
|
for (const pending of this.#pending.values())
|
|
869
1279
|
clearTimeout(pending.timer);
|
|
870
1280
|
this.#pending.clear();
|
|
@@ -880,7 +1290,11 @@ class OpfsBlockStore {
|
|
|
880
1290
|
this.#servedRequestBytes = 0;
|
|
881
1291
|
this.#leader?.crash();
|
|
882
1292
|
this.#leader = void 0;
|
|
883
|
-
this.#
|
|
1293
|
+
this.#channel?.close();
|
|
1294
|
+
this.#channel = void 0;
|
|
1295
|
+
this.#inbox?.close();
|
|
1296
|
+
this.#inbox = void 0;
|
|
1297
|
+
this.#closeAnswerChannels();
|
|
884
1298
|
this.#releaseWhenHandlesClose();
|
|
885
1299
|
}
|
|
886
1300
|
async #ensureFormatMarker() {
|
|
@@ -954,23 +1368,35 @@ for (const method of RPC_METHODS) {
|
|
|
954
1368
|
}
|
|
955
1369
|
});
|
|
956
1370
|
}
|
|
1371
|
+
const DELETE_LOCK_WAIT_MS = 1e3;
|
|
957
1372
|
async function deleteOpfsDatabase(options) {
|
|
958
1373
|
const encodedName = encodeSegment(validateStorageDatabaseName(options.name));
|
|
959
1374
|
const locks = globalThis.navigator?.locks;
|
|
960
1375
|
if (locks === void 0)
|
|
961
1376
|
throw new Error("Deleting an OPFS database requires Web Locks");
|
|
962
|
-
|
|
963
|
-
|
|
1377
|
+
const abort = new AbortController();
|
|
1378
|
+
const timer = setTimeout(() => {
|
|
1379
|
+
abort.abort();
|
|
1380
|
+
}, DELETE_LOCK_WAIT_MS);
|
|
1381
|
+
try {
|
|
1382
|
+
await locks.request(connectionLockName(options.name), { mode: "exclusive", signal: abort.signal }, async () => {
|
|
1383
|
+
const root = options.root ?? await navigator.storage.getDirectory();
|
|
1384
|
+
try {
|
|
1385
|
+
const namespace = await root.getDirectoryHandle("minnowdb");
|
|
1386
|
+
await namespace.removeEntry(encodedName, { recursive: true });
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
if (!isDomError(error, "NotFoundError"))
|
|
1389
|
+
throw error;
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
if (abort.signal.aborted && isDomError(error, "AbortError")) {
|
|
964
1394
|
throw new OpfsDatabaseInUseError(options.name);
|
|
965
|
-
const root = options.root ?? await navigator.storage.getDirectory();
|
|
966
|
-
try {
|
|
967
|
-
const namespace = await root.getDirectoryHandle("minnowdb");
|
|
968
|
-
await namespace.removeEntry(encodedName, { recursive: true });
|
|
969
|
-
} catch (error) {
|
|
970
|
-
if (!isDomError(error, "NotFoundError"))
|
|
971
|
-
throw error;
|
|
972
1395
|
}
|
|
973
|
-
|
|
1396
|
+
throw error;
|
|
1397
|
+
} finally {
|
|
1398
|
+
clearTimeout(timer);
|
|
1399
|
+
}
|
|
974
1400
|
}
|
|
975
1401
|
function connectionLockName(name) {
|
|
976
1402
|
return `minnowdb-opfs-connections:${name}`;
|
|
@@ -985,7 +1411,12 @@ async function holdConnectionLock(name) {
|
|
|
985
1411
|
})).catch(reject);
|
|
986
1412
|
});
|
|
987
1413
|
}
|
|
1414
|
+
function servedRequestKey(from, requestId) {
|
|
1415
|
+
return `${String(from.length)}:${from}${requestId}`;
|
|
1416
|
+
}
|
|
988
1417
|
const RPC_TIMED_OUT = new Error("The leader did not answer in time");
|
|
1418
|
+
const RPC_DECLINED = new Error("The connection asked is not the leader");
|
|
1419
|
+
const DECLINED_OUTCOME = { ok: false };
|
|
989
1420
|
async function resolveDatabaseRoot(options) {
|
|
990
1421
|
const encodedName = encodeSegment(validateStorageDatabaseName(options.name));
|
|
991
1422
|
const root = options.root ?? await navigator.storage.getDirectory();
|