@sproutboat/runtime 0.45.0 → 0.46.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/CHANGELOG.md +38 -0
- package/package.json +1 -1
- package/src/asset-rules.test.js +51 -30
- package/src/native-fetch-prelude.js +960 -56
- package/src/transport-embedded.js +310 -47
- package/test/fixtures/asset-redirect-cases.ts +28 -0
- package/test/fixtures/do-evict-app.js +172 -0
- package/test/native-asset-redirects.ts +80 -0
- package/test/native-cc.ts +178 -0
- package/test/native-compile.ts +53 -22
- package/test/native-db-table.ts +244 -0
- package/test/native-do-evict.ts +345 -0
- package/test/native-lto.ts +79 -0
|
@@ -3043,6 +3043,12 @@ if (globalThis.setTimeout == null) globalThis.setTimeout = setTimeout;
|
|
|
3043
3043
|
if (globalThis.clearTimeout == null) globalThis.clearTimeout = clearTimeout;
|
|
3044
3044
|
if (globalThis.setInterval == null) globalThis.setInterval = setInterval;
|
|
3045
3045
|
if (globalThis.clearInterval == null) globalThis.clearInterval = clearInterval;
|
|
3046
|
+
// The runtime's timers, for the prelude's own use. Once a Durable Object is built at the eviction date, the four globals become
|
|
3047
|
+
// wrappers that pin the object that armed a timer (__sbDOWrapTimers); a parked timer or a poll of ours must never pin anything.
|
|
3048
|
+
const __sbSetTimeout = globalThis.setTimeout;
|
|
3049
|
+
const __sbClearTimeout = globalThis.clearTimeout;
|
|
3050
|
+
const __sbSetInterval = globalThis.setInterval;
|
|
3051
|
+
const __sbClearInterval = globalThis.clearInterval;
|
|
3046
3052
|
|
|
3047
3053
|
// Prepended to every handler by tools/compile.ts, before Porffor's native-fetch
|
|
3048
3054
|
// bundled handler. Porffor's runtime/fetch-globals.js (checked through alpha-10)
|
|
@@ -3895,7 +3901,7 @@ if (typeof globalThis.EventTarget !== "function") {
|
|
|
3895
3901
|
}
|
|
3896
3902
|
static timeout(ms) {
|
|
3897
3903
|
const signal = new AbortSignal();
|
|
3898
|
-
|
|
3904
|
+
__sbSetTimeout(() => signal.__sbAbort(namedError("TimeoutError", "The operation timed out")), ms);
|
|
3899
3905
|
return signal;
|
|
3900
3906
|
}
|
|
3901
3907
|
static any(signals) {
|
|
@@ -11249,27 +11255,104 @@ function __sbMakeR2(name) {
|
|
|
11249
11255
|
};
|
|
11250
11256
|
}
|
|
11251
11257
|
|
|
11252
|
-
// Asset rules use the same parsed manifest in both transports.
|
|
11253
|
-
// @sproutboat/assets
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11258
|
+
// Asset rules use the same parsed manifest in both transports. Redirect matching mirrors
|
|
11259
|
+
// @sproutboat/assets (Cloudflare's `_redirects` rules: the whole path must match, so a trailing
|
|
11260
|
+
// slash is significant; `:name` is one or more characters up to the next "/"; one `*` is
|
|
11261
|
+
// `:splat`; a static rule beats a dynamic one); parity tests cover the portable implementation.
|
|
11262
|
+
function __sbRuleAlpha(code) {
|
|
11263
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
11264
|
+
}
|
|
11265
|
+
function __sbRuleWord(code) {
|
|
11266
|
+
return __sbRuleAlpha(code) || (code >= 48 && code <= 57) || code === 95;
|
|
11267
|
+
}
|
|
11268
|
+
function __sbRuleIsStatic(from) {
|
|
11269
|
+
for (let i = 0; i < from.length; i++) {
|
|
11270
|
+
const code = from.charCodeAt(i);
|
|
11271
|
+
if (code === 42) return false;
|
|
11272
|
+
if (code === 58 && i + 1 < from.length && __sbRuleAlpha(from.charCodeAt(i + 1))) return false;
|
|
11260
11273
|
}
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11274
|
+
return true;
|
|
11275
|
+
}
|
|
11276
|
+
// Tokens are [kind, text-or-name] pairs: 0 literal text, 1 :name, 2 splat. Null: the rule can never match.
|
|
11277
|
+
function __sbRuleTokens(from) {
|
|
11278
|
+
const tokens = [];
|
|
11279
|
+
const names = [];
|
|
11280
|
+
let text = "";
|
|
11265
11281
|
for (let i = 0; i < from.length; i++) {
|
|
11266
|
-
|
|
11267
|
-
|
|
11282
|
+
const code = from.charCodeAt(i);
|
|
11283
|
+
if (code === 42) {
|
|
11284
|
+
if (text !== "") tokens.push([0, text]);
|
|
11285
|
+
text = "";
|
|
11286
|
+
if (names.indexOf("splat") !== -1) return null;
|
|
11287
|
+
names.push("splat");
|
|
11288
|
+
tokens.push([2, "splat"]);
|
|
11289
|
+
} else if (code === 58 && i + 1 < from.length && __sbRuleAlpha(from.charCodeAt(i + 1))) {
|
|
11290
|
+
let end = i + 2;
|
|
11291
|
+
while (end < from.length && __sbRuleWord(from.charCodeAt(end))) end++;
|
|
11292
|
+
const name = from.slice(i + 1, end);
|
|
11293
|
+
if (names.indexOf(name) !== -1) return null;
|
|
11294
|
+
names.push(name);
|
|
11295
|
+
if (text !== "") tokens.push([0, text]);
|
|
11296
|
+
text = "";
|
|
11297
|
+
tokens.push([1, name]);
|
|
11298
|
+
i = end - 1;
|
|
11299
|
+
} else {
|
|
11300
|
+
text += from.charAt(i);
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
if (text !== "") tokens.push([0, text]);
|
|
11304
|
+
return tokens;
|
|
11305
|
+
}
|
|
11306
|
+
function __sbRuleMatch(tokens, t, path, p, names, values) {
|
|
11307
|
+
if (t === tokens.length) return p === path.length;
|
|
11308
|
+
const kind = tokens[t][0];
|
|
11309
|
+
const word = tokens[t][1];
|
|
11310
|
+
if (kind === 0) {
|
|
11311
|
+
if (path.slice(p, p + word.length) !== word) return false;
|
|
11312
|
+
return __sbRuleMatch(tokens, t + 1, path, p + word.length, names, values);
|
|
11313
|
+
}
|
|
11314
|
+
let limit = path.length;
|
|
11315
|
+
if (kind === 1) {
|
|
11316
|
+
for (let i = p; i < path.length; i++) {
|
|
11317
|
+
if (path.charCodeAt(i) === 47) {
|
|
11318
|
+
limit = i;
|
|
11319
|
+
break;
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
const least = kind === 1 ? p + 1 : p;
|
|
11324
|
+
for (let q = limit; q >= least; q--) {
|
|
11325
|
+
names.push(word);
|
|
11326
|
+
values.push(path.slice(p, q));
|
|
11327
|
+
if (__sbRuleMatch(tokens, t + 1, path, q, names, values)) return true;
|
|
11328
|
+
names.pop();
|
|
11329
|
+
values.pop();
|
|
11268
11330
|
}
|
|
11331
|
+
return false;
|
|
11332
|
+
}
|
|
11333
|
+
function __sbAssetRedirect(rule, path) {
|
|
11334
|
+
if (__sbRuleIsStatic(rule.from)) return rule.from === path ? rule.to : null;
|
|
11335
|
+
const tokens = __sbRuleTokens(rule.from);
|
|
11336
|
+
if (tokens === null) return null;
|
|
11337
|
+
const names = [];
|
|
11338
|
+
const values = [];
|
|
11339
|
+
if (!__sbRuleMatch(tokens, 0, path, 0, names, values)) return null;
|
|
11269
11340
|
let to = rule.to;
|
|
11270
|
-
for (
|
|
11341
|
+
for (let i = 0; i < names.length; i++) to = to.split(":" + names[i]).join(values[i]);
|
|
11271
11342
|
return to;
|
|
11272
11343
|
}
|
|
11344
|
+
// The first static rule that matches, else the first dynamic one: { status, location } or null.
|
|
11345
|
+
function __sbFindAssetRedirect(rules, path) {
|
|
11346
|
+
for (let i = 0; i < rules.length; i++) {
|
|
11347
|
+
if (__sbRuleIsStatic(rules[i].from) && rules[i].from === path) return { status: rules[i].status, location: rules[i].to };
|
|
11348
|
+
}
|
|
11349
|
+
for (let i = 0; i < rules.length; i++) {
|
|
11350
|
+
if (__sbRuleIsStatic(rules[i].from)) continue;
|
|
11351
|
+
const location = __sbAssetRedirect(rules[i], path);
|
|
11352
|
+
if (location !== null) return { status: rules[i].status, location };
|
|
11353
|
+
}
|
|
11354
|
+
return null;
|
|
11355
|
+
}
|
|
11273
11356
|
|
|
11274
11357
|
function __sbAssetHeaderMatches(pattern, path) {
|
|
11275
11358
|
const from = pattern.split("/").filter(Boolean);
|
|
@@ -11284,11 +11367,8 @@ function __sbAssetHeaderMatches(pattern, path) {
|
|
|
11284
11367
|
function __sbAssetResponse(path, r) {
|
|
11285
11368
|
if (r.location != null) return new Response(null, { status: r.status, headers: { location: r.location } });
|
|
11286
11369
|
const rules = r.rules || {};
|
|
11287
|
-
const
|
|
11288
|
-
|
|
11289
|
-
const to = __sbAssetRedirect(redirects[i], path);
|
|
11290
|
-
if (to !== null) return new Response(null, { status: redirects[i].status, headers: { location: to } });
|
|
11291
|
-
}
|
|
11370
|
+
const redirect = __sbFindAssetRedirect(rules.redirects || [], path);
|
|
11371
|
+
if (redirect !== null) return new Response(null, { status: redirect.status, headers: { location: redirect.location } });
|
|
11292
11372
|
const headers = new Headers();
|
|
11293
11373
|
if (r.type) headers.set("content-type", r.type);
|
|
11294
11374
|
if (r.found) headers.set("etag", '"' + r.hash + '"');
|
|
@@ -12202,7 +12282,7 @@ async function __sbPumpRequestBody(id, source, ended) {
|
|
|
12202
12282
|
if (bytes.length === 0) continue;
|
|
12203
12283
|
let backlog = Number(globalThis.__sbAsyncBody(id, "c", __sbToBytes(bytes)));
|
|
12204
12284
|
while (backlog > 4 * 1024 * 1024 && !ended()) {
|
|
12205
|
-
await new Promise((resolve) =>
|
|
12285
|
+
await new Promise((resolve) => __sbSetTimeout(resolve, 2));
|
|
12206
12286
|
backlog = Number(globalThis.__sbAsyncBody(id, "q", ""));
|
|
12207
12287
|
}
|
|
12208
12288
|
}
|
|
@@ -12222,10 +12302,10 @@ function __sbUpstreamStreamed(op, msg, label, signal) {
|
|
|
12222
12302
|
let done = false;
|
|
12223
12303
|
const finish = () => {
|
|
12224
12304
|
done = true;
|
|
12225
|
-
|
|
12305
|
+
__sbClearInterval(id);
|
|
12226
12306
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
12227
12307
|
};
|
|
12228
|
-
const id =
|
|
12308
|
+
const id = __sbSetInterval(() => {
|
|
12229
12309
|
if (done) return;
|
|
12230
12310
|
const raw = __sbHttpAsyncResult(id);
|
|
12231
12311
|
if (controller === null) {
|
|
@@ -12361,7 +12441,7 @@ function __sbUpstream(op, msg, label, signal, always) {
|
|
|
12361
12441
|
const settled = () => {
|
|
12362
12442
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
12363
12443
|
};
|
|
12364
|
-
const id =
|
|
12444
|
+
const id = __sbSetTimeout(() => {
|
|
12365
12445
|
settled();
|
|
12366
12446
|
try {
|
|
12367
12447
|
resolve(__sbUpstreamResponse(__sbFetchReply(__sbHttpAsyncResult(id))));
|
|
@@ -12372,7 +12452,7 @@ function __sbUpstream(op, msg, label, signal, always) {
|
|
|
12372
12452
|
if (signal) {
|
|
12373
12453
|
onAbort = () => {
|
|
12374
12454
|
settled();
|
|
12375
|
-
|
|
12455
|
+
__sbClearTimeout(id);
|
|
12376
12456
|
__sbCancelAsync(id);
|
|
12377
12457
|
reject(signal.reason);
|
|
12378
12458
|
};
|
|
@@ -12383,7 +12463,7 @@ function __sbUpstream(op, msg, label, signal, always) {
|
|
|
12383
12463
|
globalThis.__sbFetchUpstream(op, msg);
|
|
12384
12464
|
} catch (e) {
|
|
12385
12465
|
settled();
|
|
12386
|
-
|
|
12466
|
+
__sbClearTimeout(id);
|
|
12387
12467
|
reject(e);
|
|
12388
12468
|
}
|
|
12389
12469
|
});
|
|
@@ -12396,7 +12476,7 @@ function __sbUpstream(op, msg, label, signal, always) {
|
|
|
12396
12476
|
globalThis.__sbInstallFetch = function () {
|
|
12397
12477
|
__sbIngressStreamingSetup();
|
|
12398
12478
|
__sbInstallWebSockets();
|
|
12399
|
-
|
|
12479
|
+
const plainFetch = function (input, init) {
|
|
12400
12480
|
let parts;
|
|
12401
12481
|
try {
|
|
12402
12482
|
parts = __sbFetchParts(input, init);
|
|
@@ -12418,6 +12498,29 @@ globalThis.__sbInstallFetch = function () {
|
|
|
12418
12498
|
if (stream instanceof ReadableStream) return __sbCollectBody(stream).then((bytes) => send(__sbRequestBody(bytes)));
|
|
12419
12499
|
return send(__sbPartsBody(parts));
|
|
12420
12500
|
};
|
|
12501
|
+
// Inside a Durable Object's event an unawaited fetch keeps its object in memory until it settles.
|
|
12502
|
+
globalThis.fetch = function (input, init) {
|
|
12503
|
+
const rec = __sbDOL !== null ? __sbDOFrameRec() : undefined;
|
|
12504
|
+
if (rec === undefined || rec.dead) return plainFetch(input, init);
|
|
12505
|
+
__sbDOPin(rec);
|
|
12506
|
+
let p;
|
|
12507
|
+
try {
|
|
12508
|
+
p = plainFetch(input, init);
|
|
12509
|
+
} catch (error) {
|
|
12510
|
+
__sbDOUnpin(rec);
|
|
12511
|
+
throw error;
|
|
12512
|
+
}
|
|
12513
|
+
return p.then(
|
|
12514
|
+
(r) => {
|
|
12515
|
+
__sbDOUnpin(rec);
|
|
12516
|
+
return r;
|
|
12517
|
+
},
|
|
12518
|
+
(error) => {
|
|
12519
|
+
__sbDOUnpin(rec);
|
|
12520
|
+
throw error;
|
|
12521
|
+
},
|
|
12522
|
+
);
|
|
12523
|
+
};
|
|
12421
12524
|
};
|
|
12422
12525
|
|
|
12423
12526
|
// ---------------------------------------------------------------------------
|
|
@@ -12665,6 +12768,7 @@ function __sbDOCallback(fn, args) {
|
|
|
12665
12768
|
|
|
12666
12769
|
// One RPC call: method `method` with `args`, or the getter `method` when args is null.
|
|
12667
12770
|
function __sbDOInvoke(cls, id, method, args) {
|
|
12771
|
+
if (__sbDOEvictOn()) return __sbDOInvokeLive(cls, id, method, args);
|
|
12668
12772
|
let copied = null;
|
|
12669
12773
|
let inst;
|
|
12670
12774
|
try {
|
|
@@ -12691,7 +12795,38 @@ function __sbDOInvoke(cls, id, method, args) {
|
|
|
12691
12795
|
);
|
|
12692
12796
|
}
|
|
12693
12797
|
|
|
12798
|
+
// RPC into an object that may be evicted (compatibility date 2026-11-21): the event enters through __sbDORun.
|
|
12799
|
+
function __sbDOInvokeLive(cls, id, method, args) {
|
|
12800
|
+
let copied = null;
|
|
12801
|
+
try {
|
|
12802
|
+
if (args !== null) copied = __sbDOCopy(args);
|
|
12803
|
+
} catch (error) {
|
|
12804
|
+
return Promise.reject(error);
|
|
12805
|
+
}
|
|
12806
|
+
return __sbDORun(
|
|
12807
|
+
cls,
|
|
12808
|
+
id.toString(),
|
|
12809
|
+
id,
|
|
12810
|
+
(inst) => {
|
|
12811
|
+
const d = __sbDOFind(__sbDOClasses[cls], method);
|
|
12812
|
+
if (args === null) return d.get.call(inst);
|
|
12813
|
+
if (!d || !__sbIsFn(d.value)) throw new TypeError('The RPC receiver does not implement the method "' + method + '".');
|
|
12814
|
+
return d.value.apply(inst, copied);
|
|
12815
|
+
},
|
|
12816
|
+
false,
|
|
12817
|
+
).then(__sbDOCopy);
|
|
12818
|
+
}
|
|
12819
|
+
|
|
12694
12820
|
function __sbDOFetchV2(className, id, input, init) {
|
|
12821
|
+
if (__sbDOEvictOn()) {
|
|
12822
|
+
let request;
|
|
12823
|
+
try {
|
|
12824
|
+
request = new Request(input, init);
|
|
12825
|
+
} catch (error) {
|
|
12826
|
+
return Promise.reject(error);
|
|
12827
|
+
}
|
|
12828
|
+
return __sbDORun(className, id.toString(), id, (inst) => __sbDOFetchEvent(inst, request), false);
|
|
12829
|
+
}
|
|
12695
12830
|
let req;
|
|
12696
12831
|
let inst;
|
|
12697
12832
|
try {
|
|
@@ -12816,6 +12951,691 @@ function __sbGetDOInstance(cls, idText, idObj) {
|
|
|
12816
12951
|
return inst;
|
|
12817
12952
|
}
|
|
12818
12953
|
|
|
12954
|
+
// ---------------------------------------------------------------------------
|
|
12955
|
+
// Durable Object lifecycle (design 07 stage 1c, plan 018), from compatibility date 2026-11-21: an idle object
|
|
12956
|
+
// leaves memory and is constructed again by its next event, so one app can hold one object per customer.
|
|
12957
|
+
//
|
|
12958
|
+
// A record per object in a worker (not the bare instance): { cls, id, key, inst, state, gate, holder, tasks, busy,
|
|
12959
|
+
// pins, last, dead, prev, next, ... }. `busy` counts the events that entered through __sbDORun and have not
|
|
12960
|
+
// finished, their waitUntil drain included; `pins` counts things that outlive an event and cannot be rebuilt (a pending
|
|
12961
|
+
// timer, an unawaited fetch, a streamed response); open plain sockets are checked when idleness is. The records are one
|
|
12962
|
+
// least-recently-idle list (`head` the stalest). An object is evicted only when __sbDOIsIdle says so, in one synchronous
|
|
12963
|
+
// turn (__sbDOEvict): nothing in it awaits. Before the date nothing here runs and every object stays resident.
|
|
12964
|
+
//
|
|
12965
|
+
// Settings (environment; sproutd sets them from `limits ID do-idle-ms|objects|do-wait-ms`):
|
|
12966
|
+
// SB_DO_IDLE_MS how long an object sits idle before it leaves, 100 to 600000 (default 10000; 0 turns idle eviction off)
|
|
12967
|
+
// SB_DO_MAX most objects in memory in this worker (default 16 per MiB of SB_MEMORY_LIMIT, 64 to 4096; 4096 with no limit)
|
|
12968
|
+
// SB_DO_WAIT_MS how long an object's first event waits for a slot at the cap before it fails alone (default 5000)
|
|
12969
|
+
// SB_DO_QUEUE_MAX most events queued behind one object's input gate (default 1024)
|
|
12970
|
+
const __SB_DO_EVICT_DATE = "2026-11-21";
|
|
12971
|
+
const __sbDOCtxKey = {};
|
|
12972
|
+
let __sbDOL = null;
|
|
12973
|
+
|
|
12974
|
+
function __sbDOEvictOn() {
|
|
12975
|
+
return __sbCompatDate() >= __SB_DO_EVICT_DATE && __sbDOGated() && __sbDOv3();
|
|
12976
|
+
}
|
|
12977
|
+
|
|
12978
|
+
function __sbDOEnvNum(name, fallback, min, max) {
|
|
12979
|
+
const text = __sbEnv(name);
|
|
12980
|
+
if (text === "") return fallback;
|
|
12981
|
+
const n = Number(text);
|
|
12982
|
+
return n >= min && n <= max ? Math.trunc(n) : fallback;
|
|
12983
|
+
}
|
|
12984
|
+
|
|
12985
|
+
function __sbDOLife() {
|
|
12986
|
+
if (__sbDOL !== null) return __sbDOL;
|
|
12987
|
+
let idle = __sbDOEnvNum("SB_DO_IDLE_MS", 10000, 100, 600000);
|
|
12988
|
+
if (__sbEnv("SB_DO_IDLE_MS") === "0") idle = 0;
|
|
12989
|
+
const limit = Number(__sbEnv("SB_MEMORY_LIMIT"));
|
|
12990
|
+
const byLimit = limit > 0 ? Math.max(64, Math.min(4096, Math.floor(limit / 1048576) * 16)) : 4096;
|
|
12991
|
+
__sbDOL = {
|
|
12992
|
+
idle,
|
|
12993
|
+
cap: __sbDOEnvNum("SB_DO_MAX", byLimit, 1, 100000),
|
|
12994
|
+
waitMs: __sbDOEnvNum("SB_DO_WAIT_MS", 5000, 10, 600000),
|
|
12995
|
+
queueMax: __sbDOEnvNum("SB_DO_QUEUE_MAX", 1024, 1, 1000000),
|
|
12996
|
+
recs: {},
|
|
12997
|
+
head: null,
|
|
12998
|
+
tail: null,
|
|
12999
|
+
count: 0,
|
|
13000
|
+
waiters: [],
|
|
13001
|
+
sweepArmed: false,
|
|
13002
|
+
hostU: -1,
|
|
13003
|
+
restoreParked: false,
|
|
13004
|
+
restoreTries: {},
|
|
13005
|
+
noteAt: 0,
|
|
13006
|
+
noteText: "",
|
|
13007
|
+
pinned: 0,
|
|
13008
|
+
evictedIdle: 0,
|
|
13009
|
+
evictedCap: 0,
|
|
13010
|
+
evictedAbort: 0,
|
|
13011
|
+
waits: 0,
|
|
13012
|
+
waitMsMax: 0,
|
|
13013
|
+
overloaded: 0,
|
|
13014
|
+
zombie: 0,
|
|
13015
|
+
wakeUsLast: 0,
|
|
13016
|
+
wakeUsMax: 0,
|
|
13017
|
+
};
|
|
13018
|
+
__sbDOWrapTimers();
|
|
13019
|
+
return __sbDOL;
|
|
13020
|
+
}
|
|
13021
|
+
|
|
13022
|
+
function __sbDOEvictedError() {
|
|
13023
|
+
return new Error("This Durable Object instance was evicted from memory.");
|
|
13024
|
+
}
|
|
13025
|
+
|
|
13026
|
+
function __sbDOOverloaded(why) {
|
|
13027
|
+
const error = new Error("Durable Object is overloaded: " + why);
|
|
13028
|
+
error.overloaded = true;
|
|
13029
|
+
return error;
|
|
13030
|
+
}
|
|
13031
|
+
|
|
13032
|
+
// The record of the object whose event is running now (the frame the event entered carries it), or undefined.
|
|
13033
|
+
function __sbDOFrameRec() {
|
|
13034
|
+
const frame = __sbCtxGet();
|
|
13035
|
+
if (!frame) return undefined;
|
|
13036
|
+
const at = __sbFrameIndex(frame, __sbDOCtxKey);
|
|
13037
|
+
return at === -1 ? undefined : frame.vals[at];
|
|
13038
|
+
}
|
|
13039
|
+
|
|
13040
|
+
// ---- the list: head is the stalest record, tail the freshest ----
|
|
13041
|
+
function __sbDOLink(L, rec) {
|
|
13042
|
+
rec.prev = L.tail;
|
|
13043
|
+
rec.next = null;
|
|
13044
|
+
if (L.tail !== null) L.tail.next = rec;
|
|
13045
|
+
else L.head = rec;
|
|
13046
|
+
L.tail = rec;
|
|
13047
|
+
L.count++;
|
|
13048
|
+
}
|
|
13049
|
+
function __sbDOUnlink(L, rec) {
|
|
13050
|
+
if (rec.prev !== null) rec.prev.next = rec.next;
|
|
13051
|
+
else L.head = rec.next;
|
|
13052
|
+
if (rec.next !== null) rec.next.prev = rec.prev;
|
|
13053
|
+
else L.tail = rec.prev;
|
|
13054
|
+
rec.prev = null;
|
|
13055
|
+
rec.next = null;
|
|
13056
|
+
L.count--;
|
|
13057
|
+
}
|
|
13058
|
+
// Becomes idle now: the freshest record.
|
|
13059
|
+
function __sbDOFresh(L, rec, now) {
|
|
13060
|
+
rec.last = now === undefined ? Date.now() : now;
|
|
13061
|
+
if (L.tail === rec) return;
|
|
13062
|
+
__sbDOUnlink(L, rec);
|
|
13063
|
+
__sbDOLink(L, rec);
|
|
13064
|
+
}
|
|
13065
|
+
|
|
13066
|
+
// ---- idleness ----
|
|
13067
|
+
function __sbDOSocketsFree(L, rec) {
|
|
13068
|
+
const plain = rec.plain;
|
|
13069
|
+
if (plain.length > 0) {
|
|
13070
|
+
for (let i = plain.length - 1; i >= 0; i--) if (plain[i]._state === __SB_WS_CLOSED) plain.splice(i, 1);
|
|
13071
|
+
if (plain.length > 0) return false;
|
|
13072
|
+
}
|
|
13073
|
+
const tcp = rec.tcp;
|
|
13074
|
+
if (tcp.length > 0) {
|
|
13075
|
+
for (let i = tcp.length - 1; i >= 0; i--) if (tcp[i].id === 0) tcp.splice(i, 1);
|
|
13076
|
+
if (tcp.length > 0) return false;
|
|
13077
|
+
}
|
|
13078
|
+
const sockets = rec.holder.sockets;
|
|
13079
|
+
for (let i = 0; i < sockets.length; i++) {
|
|
13080
|
+
const server = sockets[i];
|
|
13081
|
+
if (server._state === __SB_WS_CLOSED) continue;
|
|
13082
|
+
const client = server._peer;
|
|
13083
|
+
// A pair whose two ends are both in the worker, or no host that takes a socket back: nothing outside holds it.
|
|
13084
|
+
if (client === null || client._host === 0) return false;
|
|
13085
|
+
if (L.hostU < 0) L.hostU = __sbWsOp("U", 0, "", 0) === "1" ? 1 : 0;
|
|
13086
|
+
if (L.hostU !== 1) return false;
|
|
13087
|
+
// An event that left the bridge and exists only in this worker (the bridge keeps the rest of the queue).
|
|
13088
|
+
if (client._inbox.length > 0 || server._inbox.length > 0 || client._flushing || server._flushing) return false;
|
|
13089
|
+
}
|
|
13090
|
+
return true;
|
|
13091
|
+
}
|
|
13092
|
+
function __sbDOIsIdle(L, rec) {
|
|
13093
|
+
const gate = rec.gate;
|
|
13094
|
+
return (
|
|
13095
|
+
rec.busy === 0 &&
|
|
13096
|
+
rec.pins === 0 &&
|
|
13097
|
+
gate.locks === 0 &&
|
|
13098
|
+
gate.waiting.length === 0 &&
|
|
13099
|
+
!gate.txnBusy &&
|
|
13100
|
+
gate.txnQueue.length === 0 &&
|
|
13101
|
+
__sbDOSocketsFree(L, rec)
|
|
13102
|
+
);
|
|
13103
|
+
}
|
|
13104
|
+
|
|
13105
|
+
function __sbDOPin(rec) {
|
|
13106
|
+
rec.pins++;
|
|
13107
|
+
if (rec.pins === 1) {
|
|
13108
|
+
rec.pinSince = Date.now();
|
|
13109
|
+
__sbDOL.pinned++;
|
|
13110
|
+
}
|
|
13111
|
+
}
|
|
13112
|
+
function __sbDOUnpin(rec) {
|
|
13113
|
+
if (rec.pins === 0) return;
|
|
13114
|
+
rec.pins--;
|
|
13115
|
+
if (rec.pins !== 0) return;
|
|
13116
|
+
const L = __sbDOL;
|
|
13117
|
+
if (rec.dead) return;
|
|
13118
|
+
L.pinned--;
|
|
13119
|
+
rec.pinLogged = false;
|
|
13120
|
+
if (rec.busy === 0) {
|
|
13121
|
+
__sbDOFresh(L, rec);
|
|
13122
|
+
__sbDOServe(L);
|
|
13123
|
+
__sbDOArmSweep(L);
|
|
13124
|
+
}
|
|
13125
|
+
}
|
|
13126
|
+
|
|
13127
|
+
// ---- eviction: one synchronous turn, nothing awaits ----
|
|
13128
|
+
// The reason says why: "idle", "cap" (a slot was needed), "abort" (state.abort()), "bcw" (blockConcurrencyWhile failed).
|
|
13129
|
+
// `force` skips the idleness check and closes the object's sockets (abort and reset); otherwise sockets go back to the host.
|
|
13130
|
+
function __sbDOEvict(rec, reason, force) {
|
|
13131
|
+
if (rec.dead) return false;
|
|
13132
|
+
const L = __sbDOL;
|
|
13133
|
+
if (!force && !__sbDOIsIdle(L, rec)) return false;
|
|
13134
|
+
if (rec.registered) {
|
|
13135
|
+
delete L.recs[rec.key];
|
|
13136
|
+
__sbDOUnlink(L, rec);
|
|
13137
|
+
rec.registered = false;
|
|
13138
|
+
if (rec.pins > 0) L.pinned--;
|
|
13139
|
+
}
|
|
13140
|
+
// Revoke: every state method of this instance now throws the evicted error, so a leftover closure cannot write as a second instance.
|
|
13141
|
+
rec.dead = true;
|
|
13142
|
+
__sbDOReleaseSockets(L, rec, force);
|
|
13143
|
+
if (rec.usedSql) {
|
|
13144
|
+
try {
|
|
13145
|
+
__sbRpc("do.sql.close", { cls: rec.cls, file: rec.sqlFile });
|
|
13146
|
+
} catch (error) {
|
|
13147
|
+
__sbLogUncaught("do.sql.close", error);
|
|
13148
|
+
}
|
|
13149
|
+
}
|
|
13150
|
+
const holder = rec.holder;
|
|
13151
|
+
if (holder !== null) {
|
|
13152
|
+
holder.inst = null;
|
|
13153
|
+
holder.rec = null;
|
|
13154
|
+
holder.sockets = [];
|
|
13155
|
+
}
|
|
13156
|
+
rec.inst = null;
|
|
13157
|
+
rec.state = null;
|
|
13158
|
+
rec.plain = [];
|
|
13159
|
+
rec.tcp = [];
|
|
13160
|
+
if (reason === "idle") L.evictedIdle++;
|
|
13161
|
+
else if (reason === "cap") L.evictedCap++;
|
|
13162
|
+
else L.evictedAbort++;
|
|
13163
|
+
return true;
|
|
13164
|
+
}
|
|
13165
|
+
|
|
13166
|
+
// The object's hibernatable sockets go back to the host (op g: unclaimed, their queued events kept), or are closed for a reset.
|
|
13167
|
+
function __sbDOReleaseSockets(L, rec, force) {
|
|
13168
|
+
const holder = rec.holder;
|
|
13169
|
+
if (holder === null) return;
|
|
13170
|
+
const sockets = holder.sockets;
|
|
13171
|
+
for (let i = 0; i < sockets.length; i++) {
|
|
13172
|
+
const server = sockets[i];
|
|
13173
|
+
const client = server._peer;
|
|
13174
|
+
if (client !== null && client._pumpTimer !== undefined) {
|
|
13175
|
+
__sbClearTimeout(client._pumpTimer);
|
|
13176
|
+
client._pumpTimer = undefined;
|
|
13177
|
+
}
|
|
13178
|
+
if (server._state === __SB_WS_CLOSED) continue;
|
|
13179
|
+
if (force) {
|
|
13180
|
+
try {
|
|
13181
|
+
server.close(1011, "Durable Object reset");
|
|
13182
|
+
} catch {
|
|
13183
|
+
/* already closing */
|
|
13184
|
+
}
|
|
13185
|
+
continue;
|
|
13186
|
+
}
|
|
13187
|
+
if (client !== null && client._host > 0) {
|
|
13188
|
+
const hostId = client._host;
|
|
13189
|
+
client._host = 0;
|
|
13190
|
+
server._state = __SB_WS_CLOSED;
|
|
13191
|
+
if (!L.restoreParked) {
|
|
13192
|
+
L.restoreParked = true;
|
|
13193
|
+
__sbWsRestorePark();
|
|
13194
|
+
}
|
|
13195
|
+
__sbWsOp("g", hostId, "", 0);
|
|
13196
|
+
}
|
|
13197
|
+
}
|
|
13198
|
+
}
|
|
13199
|
+
|
|
13200
|
+
// state.abort() and a failed blockConcurrencyWhile(): the object is forgotten (storage kept) and this instance is revoked.
|
|
13201
|
+
function __sbDOReset(rec, reason, error) {
|
|
13202
|
+
if (rec.dead) return;
|
|
13203
|
+
const L = __sbDOL;
|
|
13204
|
+
rec.deadError = error === undefined ? null : error;
|
|
13205
|
+
__sbDOEvict(rec, reason, true);
|
|
13206
|
+
// Events waiting at its gate now fail (each finds the record dead).
|
|
13207
|
+
rec.gate.locks = 0;
|
|
13208
|
+
__sbDOPump(rec.gate);
|
|
13209
|
+
__sbDOServe(L);
|
|
13210
|
+
}
|
|
13211
|
+
|
|
13212
|
+
function __sbDOStalest(L) {
|
|
13213
|
+
for (let rec = L.head; rec !== null; rec = rec.next) if (__sbDOIsIdle(L, rec)) return rec;
|
|
13214
|
+
return null;
|
|
13215
|
+
}
|
|
13216
|
+
|
|
13217
|
+
// The sweep: idle objects older than the timeout leave, from the stale end of the list. Bounded twice: `limit` records are looked at and at
|
|
13218
|
+
// most `most` leave, because an eviction closes a SQLite handle (a checkpoint and its fsyncs, about a millisecond) and the whole sweep is one
|
|
13219
|
+
// synchronous turn: 500 objects going at once would stop every request for half a second or more (measured: p99 1.9 s in an alarm storm).
|
|
13220
|
+
// What is left goes on the next tick; the cap evicts one object at a time when a slot is needed in the meantime.
|
|
13221
|
+
function __sbDOSweep(L, limit, most) {
|
|
13222
|
+
if (L.idle === 0) return;
|
|
13223
|
+
const now = Date.now();
|
|
13224
|
+
let seen = 0;
|
|
13225
|
+
let left = most;
|
|
13226
|
+
for (let rec = L.head; rec !== null && seen < limit && left > 0; ) {
|
|
13227
|
+
const next = rec.next;
|
|
13228
|
+
seen++;
|
|
13229
|
+
if (rec.busy === 0) {
|
|
13230
|
+
if (now - rec.last < L.idle) break;
|
|
13231
|
+
if (rec.pins > 0) {
|
|
13232
|
+
if (!rec.pinLogged && now - rec.pinSince > 60000) {
|
|
13233
|
+
rec.pinLogged = true;
|
|
13234
|
+
console.error("Durable Object " + rec.cls + " has been held in memory for over 60 s by a timer, a connection or a streamed response; it cannot be evicted while that lives");
|
|
13235
|
+
}
|
|
13236
|
+
} else if (__sbDOEvict(rec, "idle", false)) left--;
|
|
13237
|
+
}
|
|
13238
|
+
rec = next;
|
|
13239
|
+
}
|
|
13240
|
+
}
|
|
13241
|
+
|
|
13242
|
+
// The sweep timer is parked at the host (op D): the host fires it when its deadline passes, and it is not background work, so
|
|
13243
|
+
// an app whose objects are all evicted is still reaped on time. A host without the op (an older sproutd) gets no timer: objects
|
|
13244
|
+
// then leave when the next event arrives. A standalone binary has no reaper, so a plain timer does it.
|
|
13245
|
+
function __sbDOArmSweep(L) {
|
|
13246
|
+
if (L.sweepArmed || L.idle === 0 || L.count === 0) return;
|
|
13247
|
+
let delay = L.idle;
|
|
13248
|
+
if (L.head !== null) delay = Math.max(50, Math.min(L.idle, L.head.last + L.idle - Date.now()));
|
|
13249
|
+
const timer = __sbSetTimeout(__sbDOSweepFire, 2147483647);
|
|
13250
|
+
const parked = __sbWsOp("D", Math.trunc(delay), "", timer) === "0";
|
|
13251
|
+
if (parked) {
|
|
13252
|
+
L.sweepArmed = true;
|
|
13253
|
+
return;
|
|
13254
|
+
}
|
|
13255
|
+
__sbClearTimeout(timer);
|
|
13256
|
+
if (!__sbHosted()) {
|
|
13257
|
+
L.sweepArmed = true;
|
|
13258
|
+
__sbSetTimeout(__sbDOSweepFire, delay);
|
|
13259
|
+
}
|
|
13260
|
+
}
|
|
13261
|
+
function __sbDOSweepFire() {
|
|
13262
|
+
const L = __sbDOL;
|
|
13263
|
+
if (L === null) return;
|
|
13264
|
+
L.sweepArmed = false;
|
|
13265
|
+
const before = L.evictedIdle;
|
|
13266
|
+
__sbDOSweep(L, 100000, 24);
|
|
13267
|
+
// Objects left: SQLite hands the pages of the databases still open back too (the quiet collection follows this timer).
|
|
13268
|
+
if (L.evictedIdle !== before) {
|
|
13269
|
+
try {
|
|
13270
|
+
__sbRpc("do.release", {});
|
|
13271
|
+
} catch (error) {
|
|
13272
|
+
__sbLogUncaught("do.release", error);
|
|
13273
|
+
}
|
|
13274
|
+
}
|
|
13275
|
+
__sbDOServe(L);
|
|
13276
|
+
__sbDOArmSweep(L);
|
|
13277
|
+
__sbDONote(L, true);
|
|
13278
|
+
}
|
|
13279
|
+
|
|
13280
|
+
// ---- acquire: the record of an object, constructing it when it has none ----
|
|
13281
|
+
function __sbDOResolveId(cls, idText, idObj) {
|
|
13282
|
+
const stateId = idObj || (idText.indexOf("name:") === 0 ? __sbDONamedId(cls, idText.slice(5)) : __sbDOMakeId(idText, undefined));
|
|
13283
|
+
return stateId;
|
|
13284
|
+
}
|
|
13285
|
+
|
|
13286
|
+
// Returns the record, or a promise of one when the cap is full and nothing is idle.
|
|
13287
|
+
function __sbDOAcquire(cls, idText, idObj) {
|
|
13288
|
+
const L = __sbDOLife();
|
|
13289
|
+
// The common case: the caller's id text is the object's canonical id, so the record is one lookup away.
|
|
13290
|
+
const quick = L.recs[cls + " " + idText];
|
|
13291
|
+
if (quick !== undefined) return quick;
|
|
13292
|
+
if (!__sbDOClasses[cls]) throw new Error("no such Durable Object class: " + cls);
|
|
13293
|
+
const stateId = __sbDOResolveId(cls, idText, idObj);
|
|
13294
|
+
const id = stateId.toString();
|
|
13295
|
+
const key = cls + " " + id;
|
|
13296
|
+
const rec = L.recs[key];
|
|
13297
|
+
if (rec !== undefined) return rec;
|
|
13298
|
+
__sbDOSweep(L, 8, 4);
|
|
13299
|
+
if (L.count >= L.cap) {
|
|
13300
|
+
const victim = __sbDOStalest(L);
|
|
13301
|
+
if (victim === null) return __sbDOWait(L, cls, id, stateId, key);
|
|
13302
|
+
__sbDOEvict(victim, "cap", false);
|
|
13303
|
+
}
|
|
13304
|
+
return __sbDOBuild(L, cls, id, stateId, key);
|
|
13305
|
+
}
|
|
13306
|
+
|
|
13307
|
+
function __sbDOWait(L, cls, id, stateId, key) {
|
|
13308
|
+
L.waits++;
|
|
13309
|
+
return new Promise((resolve, reject) => {
|
|
13310
|
+
const w = { cls, id, stateId, key, resolve, reject, timer: 0, done: false, t0: Date.now() };
|
|
13311
|
+
w.timer = __sbSetTimeout(() => {
|
|
13312
|
+
if (w.done) return;
|
|
13313
|
+
w.done = true;
|
|
13314
|
+
const at = L.waiters.indexOf(w);
|
|
13315
|
+
if (at >= 0) L.waiters.splice(at, 1);
|
|
13316
|
+
L.overloaded++;
|
|
13317
|
+
reject(__sbDOOverloaded("too many objects are awake in this app"));
|
|
13318
|
+
}, L.waitMs);
|
|
13319
|
+
L.waiters.push(w);
|
|
13320
|
+
});
|
|
13321
|
+
}
|
|
13322
|
+
|
|
13323
|
+
// A slot or an idle object freed: serve the waiting first events in order.
|
|
13324
|
+
function __sbDOServe(L) {
|
|
13325
|
+
while (L.waiters.length > 0) {
|
|
13326
|
+
const w = L.waiters[0];
|
|
13327
|
+
let rec = L.recs[w.key];
|
|
13328
|
+
if (rec === undefined) {
|
|
13329
|
+
if (L.count >= L.cap) {
|
|
13330
|
+
const victim = __sbDOStalest(L);
|
|
13331
|
+
if (victim === null) return;
|
|
13332
|
+
__sbDOEvict(victim, "cap", false);
|
|
13333
|
+
}
|
|
13334
|
+
}
|
|
13335
|
+
L.waiters.shift();
|
|
13336
|
+
w.done = true;
|
|
13337
|
+
__sbClearTimeout(w.timer);
|
|
13338
|
+
const waited = Date.now() - w.t0;
|
|
13339
|
+
if (waited > L.waitMsMax) L.waitMsMax = waited;
|
|
13340
|
+
try {
|
|
13341
|
+
if (rec === undefined) rec = __sbDOBuild(L, w.cls, w.id, w.stateId, w.key);
|
|
13342
|
+
w.resolve(rec);
|
|
13343
|
+
} catch (error) {
|
|
13344
|
+
w.reject(error);
|
|
13345
|
+
}
|
|
13346
|
+
}
|
|
13347
|
+
}
|
|
13348
|
+
|
|
13349
|
+
// A clock finer than Date.now() for the wake counter (microseconds); the performance object is built on its first read.
|
|
13350
|
+
function __sbDONow() {
|
|
13351
|
+
const p = globalThis.performance;
|
|
13352
|
+
return p && __sbIsFn(p.now) ? p.now() : Date.now();
|
|
13353
|
+
}
|
|
13354
|
+
|
|
13355
|
+
function __sbDOBuild(L, cls, id, stateId, key) {
|
|
13356
|
+
const Ctor = __sbDOClasses[cls];
|
|
13357
|
+
const t0 = __sbDONow();
|
|
13358
|
+
if (stateId.name !== undefined) __sbDOAdopt(cls, "name:" + stateId.name, id);
|
|
13359
|
+
const tasks = [];
|
|
13360
|
+
const gate = __sbDOMakeGate();
|
|
13361
|
+
const rec = {
|
|
13362
|
+
cls,
|
|
13363
|
+
id,
|
|
13364
|
+
key,
|
|
13365
|
+
inst: null,
|
|
13366
|
+
state: null,
|
|
13367
|
+
gate,
|
|
13368
|
+
holder: null,
|
|
13369
|
+
tasks,
|
|
13370
|
+
busy: 0,
|
|
13371
|
+
pins: 0,
|
|
13372
|
+
pinSince: 0,
|
|
13373
|
+
pinLogged: false,
|
|
13374
|
+
last: Date.now(),
|
|
13375
|
+
usedSql: false,
|
|
13376
|
+
sqlFile: "",
|
|
13377
|
+
dead: false,
|
|
13378
|
+
registered: false,
|
|
13379
|
+
prev: null,
|
|
13380
|
+
next: null,
|
|
13381
|
+
frame: null,
|
|
13382
|
+
plain: [],
|
|
13383
|
+
tcp: [],
|
|
13384
|
+
deadError: null,
|
|
13385
|
+
};
|
|
13386
|
+
rec.frame = __sbFrameWith(undefined, __sbDOCtxKey, rec);
|
|
13387
|
+
const state = {
|
|
13388
|
+
id: stateId,
|
|
13389
|
+
storage: __sbDOAsyncStorage(cls, id, gate, rec),
|
|
13390
|
+
blockConcurrencyWhile(fn) {
|
|
13391
|
+
if (!__sbIsFn(fn))
|
|
13392
|
+
throw new TypeError("Failed to execute 'blockConcurrencyWhile' on 'DurableObjectState': parameter 1 is not of type 'function'.");
|
|
13393
|
+
if (rec.dead) throw __sbDOEvictedError();
|
|
13394
|
+
// No event enters until fn settles; a throw or 30 s without settling resets the object, as in workerd.
|
|
13395
|
+
gate.locks++;
|
|
13396
|
+
let settled = false;
|
|
13397
|
+
const done = () => {
|
|
13398
|
+
settled = true;
|
|
13399
|
+
__sbClearTimeout(timer);
|
|
13400
|
+
if (!rec.dead) {
|
|
13401
|
+
gate.locks--;
|
|
13402
|
+
__sbDOPump(gate);
|
|
13403
|
+
}
|
|
13404
|
+
};
|
|
13405
|
+
const timer = __sbSetTimeout(() => {
|
|
13406
|
+
if (settled) return;
|
|
13407
|
+
settled = true;
|
|
13408
|
+
console.error("blockConcurrencyWhile() did not settle in 30 seconds: the Durable Object " + cls + " was reset");
|
|
13409
|
+
__sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() did not settle in 30 seconds."));
|
|
13410
|
+
}, 30000);
|
|
13411
|
+
let r;
|
|
13412
|
+
try {
|
|
13413
|
+
r = fn();
|
|
13414
|
+
} catch (error) {
|
|
13415
|
+
done();
|
|
13416
|
+
__sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() threw: " + String((error && error.message) || error)));
|
|
13417
|
+
return Promise.reject(error);
|
|
13418
|
+
}
|
|
13419
|
+
return Promise.resolve(r).then(
|
|
13420
|
+
(v) => {
|
|
13421
|
+
done();
|
|
13422
|
+
return v;
|
|
13423
|
+
},
|
|
13424
|
+
(error) => {
|
|
13425
|
+
done();
|
|
13426
|
+
__sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() failed: " + String((error && error.message) || error)));
|
|
13427
|
+
throw error;
|
|
13428
|
+
},
|
|
13429
|
+
);
|
|
13430
|
+
},
|
|
13431
|
+
waitUntil(p) {
|
|
13432
|
+
if (rec.dead) throw __sbDOEvictedError();
|
|
13433
|
+
if (p && __sbIsFn(p.then)) tasks.push(p);
|
|
13434
|
+
},
|
|
13435
|
+
};
|
|
13436
|
+
state.props = {};
|
|
13437
|
+
state.abort = (reason) => {
|
|
13438
|
+
__sbDOReset(rec, "abort");
|
|
13439
|
+
throw new Error(reason == null ? "Durable Object was aborted." : String(reason));
|
|
13440
|
+
};
|
|
13441
|
+
const holder = { inst: null, rec, auto: null, cls, id, restored: [], sockets: [] };
|
|
13442
|
+
rec.holder = holder;
|
|
13443
|
+
rec.state = state;
|
|
13444
|
+
if (__sbWsEnabled()) __sbDOWebSocketState(state, holder);
|
|
13445
|
+
let inst;
|
|
13446
|
+
try {
|
|
13447
|
+
inst = new Ctor(state, __sbEnvValue());
|
|
13448
|
+
} catch (error) {
|
|
13449
|
+
rec.dead = true;
|
|
13450
|
+
throw error;
|
|
13451
|
+
}
|
|
13452
|
+
// blockConcurrencyWhile() reset the object while its constructor was still running (a synchronous throw).
|
|
13453
|
+
if (rec.dead) throw new Error("The Durable Object " + cls + " was reset while it was starting.");
|
|
13454
|
+
holder.inst = inst;
|
|
13455
|
+
inst.__sbTasks = tasks;
|
|
13456
|
+
inst.__sbGate = gate;
|
|
13457
|
+
rec.inst = inst;
|
|
13458
|
+
inst.__sbRec = rec;
|
|
13459
|
+
L.recs[key] = rec;
|
|
13460
|
+
rec.registered = true;
|
|
13461
|
+
__sbDOLink(L, rec);
|
|
13462
|
+
const wake = Math.round((__sbDONow() - t0) * 1000);
|
|
13463
|
+
L.wakeUsLast = wake;
|
|
13464
|
+
if (wake > L.wakeUsMax) L.wakeUsMax = wake;
|
|
13465
|
+
// Sockets restored from hibernation start delivering now that the object exists.
|
|
13466
|
+
const restored = holder.restored;
|
|
13467
|
+
holder.restored = [];
|
|
13468
|
+
for (let i = 0; i < restored.length; i++) __sbWsPump(restored[i]);
|
|
13469
|
+
__sbDOArmSweep(L);
|
|
13470
|
+
return rec;
|
|
13471
|
+
}
|
|
13472
|
+
|
|
13473
|
+
// ---- run an event: the one entry for RPC, stub.fetch, alarms and socket events ----
|
|
13474
|
+
// `run(inst)` is the event; it enters the object's gate and runs in a frame that names the object. The returned promise settles with
|
|
13475
|
+
// the event's value. The object counts as busy until the event and the drain of its waitUntil tasks have settled (`wait`: the
|
|
13476
|
+
// returned promise waits for that drain too).
|
|
13477
|
+
function __sbDORun(cls, idText, idObj, run, wait) {
|
|
13478
|
+
let acquired;
|
|
13479
|
+
try {
|
|
13480
|
+
acquired = __sbDOAcquire(cls, idText, idObj);
|
|
13481
|
+
} catch (error) {
|
|
13482
|
+
return Promise.reject(error);
|
|
13483
|
+
}
|
|
13484
|
+
if (__sbIsFn(acquired.then)) return acquired.then((rec) => __sbDOStart(rec, run, wait));
|
|
13485
|
+
return __sbDOStart(acquired, run, wait);
|
|
13486
|
+
}
|
|
13487
|
+
|
|
13488
|
+
function __sbDOStart(rec, run, wait) {
|
|
13489
|
+
const L = __sbDOL;
|
|
13490
|
+
if (rec.dead) return Promise.reject(rec.deadError !== null ? rec.deadError : __sbDOEvictedError());
|
|
13491
|
+
const gate = rec.gate;
|
|
13492
|
+
if (gate.waiting.length >= L.queueMax) {
|
|
13493
|
+
L.overloaded++;
|
|
13494
|
+
return Promise.reject(__sbDOOverloaded("too many events are queued for this object"));
|
|
13495
|
+
}
|
|
13496
|
+
rec.busy++;
|
|
13497
|
+
// The input gate, as __sbDOEnter has it: an open gate runs the event now, a shut one queues it behind the others.
|
|
13498
|
+
if (gate.locks === 0 && gate.waiting.length === 0) return __sbDOEvent(L, rec, run, wait);
|
|
13499
|
+
return new Promise((resolve) => {
|
|
13500
|
+
gate.waiting.push(() => {
|
|
13501
|
+
resolve(__sbDOEvent(L, rec, run, wait));
|
|
13502
|
+
});
|
|
13503
|
+
});
|
|
13504
|
+
}
|
|
13505
|
+
|
|
13506
|
+
// One event, in the object's frame; busy until it and the drain of its waitUntil tasks have settled. One coroutine and nothing else
|
|
13507
|
+
// per event: this path is paid by every call into every object.
|
|
13508
|
+
async function __sbDOEvent(L, rec, run, wait) {
|
|
13509
|
+
try {
|
|
13510
|
+
if (rec.dead) throw rec.deadError !== null ? rec.deadError : __sbDOEvictedError();
|
|
13511
|
+
return await __sbRunInFrame(rec.frame, run, [rec.inst]);
|
|
13512
|
+
} finally {
|
|
13513
|
+
const drained = __sbDOSettle(L, rec, wait);
|
|
13514
|
+
if (drained !== undefined) await drained;
|
|
13515
|
+
}
|
|
13516
|
+
}
|
|
13517
|
+
|
|
13518
|
+
// The event is over: drain its waitUntil tasks (it stays busy until they settle), then it may become idle.
|
|
13519
|
+
function __sbDOSettle(L, rec, wait) {
|
|
13520
|
+
if (rec.tasks.length > 0) {
|
|
13521
|
+
const finish = () => __sbDOIdle(L, rec);
|
|
13522
|
+
const drained = __sbDrainWaitUntil(rec.tasks);
|
|
13523
|
+
drained.then(finish, finish);
|
|
13524
|
+
return wait ? drained.then(undefined, () => {}) : undefined;
|
|
13525
|
+
}
|
|
13526
|
+
__sbDOIdle(L, rec);
|
|
13527
|
+
return undefined;
|
|
13528
|
+
}
|
|
13529
|
+
function __sbDOIdle(L, rec) {
|
|
13530
|
+
rec.busy--;
|
|
13531
|
+
if (rec.busy !== 0 || rec.dead) return;
|
|
13532
|
+
const now = Date.now();
|
|
13533
|
+
if (rec.pins === 0) __sbDOFresh(L, rec, now);
|
|
13534
|
+
if (L.waiters.length > 0) __sbDOServe(L);
|
|
13535
|
+
if (!L.sweepArmed) __sbDOArmSweep(L);
|
|
13536
|
+
if (now - L.noteAt >= 1000) __sbDONote(L, true);
|
|
13537
|
+
}
|
|
13538
|
+
|
|
13539
|
+
// ---- pins: a timer, an unawaited fetch and a streamed response keep their object in memory ----
|
|
13540
|
+
// The timer globals become wrappers once the first object is built. The prelude's own timers use __sbSetTimeout and the rest,
|
|
13541
|
+
// which are the runtime's, so a parked timer of ours never pins anything.
|
|
13542
|
+
function __sbDOWrapTimers() {
|
|
13543
|
+
const armed = new Map();
|
|
13544
|
+
const arm = (original, repeat) =>
|
|
13545
|
+
function (callback, delay, ...args) {
|
|
13546
|
+
const rec = __sbDOFrameRec();
|
|
13547
|
+
if (rec === undefined || rec.dead || !__sbIsFn(callback)) return original(callback, delay, ...args);
|
|
13548
|
+
const slot = { id: 0, rec };
|
|
13549
|
+
__sbDOPin(rec);
|
|
13550
|
+
const fired = repeat
|
|
13551
|
+
? callback
|
|
13552
|
+
: function (...fireArgs) {
|
|
13553
|
+
armed.delete(slot.id);
|
|
13554
|
+
__sbDOUnpin(slot.rec);
|
|
13555
|
+
return callback.apply(undefined, fireArgs);
|
|
13556
|
+
};
|
|
13557
|
+
slot.id = Number(original(fired, delay, ...args));
|
|
13558
|
+
armed.set(slot.id, slot);
|
|
13559
|
+
return slot.id;
|
|
13560
|
+
};
|
|
13561
|
+
const clear = (original) =>
|
|
13562
|
+
function (id) {
|
|
13563
|
+
const key = Number(id);
|
|
13564
|
+
const slot = armed.get(key);
|
|
13565
|
+
if (slot !== undefined) {
|
|
13566
|
+
armed.delete(key);
|
|
13567
|
+
__sbDOUnpin(slot.rec);
|
|
13568
|
+
}
|
|
13569
|
+
return original(id);
|
|
13570
|
+
};
|
|
13571
|
+
globalThis.setTimeout = arm(__sbSetTimeout, false);
|
|
13572
|
+
globalThis.setInterval = arm(__sbSetInterval, true);
|
|
13573
|
+
globalThis.clearTimeout = clear(__sbClearTimeout);
|
|
13574
|
+
globalThis.clearInterval = clear(__sbClearInterval);
|
|
13575
|
+
}
|
|
13576
|
+
|
|
13577
|
+
// ---- counters: cheap, one host note a second at most, only when changed ----
|
|
13578
|
+
function __sbDOStats(L) {
|
|
13579
|
+
let sqlOpen = 0;
|
|
13580
|
+
try {
|
|
13581
|
+
sqlOpen = Number(__sbRpc("do.stats", {}).stats.doOpen) || 0;
|
|
13582
|
+
} catch {
|
|
13583
|
+
sqlOpen = 0;
|
|
13584
|
+
}
|
|
13585
|
+
return (
|
|
13586
|
+
"objects_awake=" + L.count +
|
|
13587
|
+
" objects_pinned=" + L.pinned +
|
|
13588
|
+
" do_sql_open=" + sqlOpen +
|
|
13589
|
+
" evicted_idle=" + L.evictedIdle +
|
|
13590
|
+
" evicted_cap=" + L.evictedCap +
|
|
13591
|
+
" evicted_abort=" + L.evictedAbort +
|
|
13592
|
+
" waits=" + L.waits +
|
|
13593
|
+
" wait_ms_max=" + L.waitMsMax +
|
|
13594
|
+
" overloaded=" + L.overloaded +
|
|
13595
|
+
" zombie_calls=" + L.zombie +
|
|
13596
|
+
" wake_us=" + L.wakeUsLast + "," + L.wakeUsMax
|
|
13597
|
+
);
|
|
13598
|
+
}
|
|
13599
|
+
function __sbDONote(L, force) {
|
|
13600
|
+
const now = Date.now();
|
|
13601
|
+
if (!force && now - L.noteAt < 1000) return;
|
|
13602
|
+
L.noteAt = now;
|
|
13603
|
+
const text = __sbDOStats(L);
|
|
13604
|
+
if (text === L.noteText) return;
|
|
13605
|
+
L.noteText = text;
|
|
13606
|
+
if (__sbHosted()) __sbHostNote("do " + text);
|
|
13607
|
+
if (__sbEnv("SB_DO_STATS") === "1") console.error("do " + text);
|
|
13608
|
+
}
|
|
13609
|
+
|
|
13610
|
+
// A revoked state method: the evicted (or reset) object's instance cannot touch storage any more.
|
|
13611
|
+
function __sbDOZombie(rec) {
|
|
13612
|
+
__sbDOL.zombie++;
|
|
13613
|
+
return rec.deadError !== null ? rec.deadError : __sbDOEvictedError();
|
|
13614
|
+
}
|
|
13615
|
+
function __sbDOFetchEvent(inst, req) {
|
|
13616
|
+
return __sbFetchWithDrain(inst.fetch(req), [], false, true);
|
|
13617
|
+
}
|
|
13618
|
+
function __sbWsRestorePark() {
|
|
13619
|
+
const timer = __sbSetTimeout(__sbWsRestoreLoop, 2147483647);
|
|
13620
|
+
__sbWsOp("R", 0, "", timer);
|
|
13621
|
+
}
|
|
13622
|
+
function __sbWsRestoreFailed(id, error) {
|
|
13623
|
+
__sbLogUncaught("websocket", error);
|
|
13624
|
+
if (!(error && error.overloaded === true)) return;
|
|
13625
|
+
const L = __sbDOL;
|
|
13626
|
+
const tries = (L.restoreTries[id] || 0) + 1;
|
|
13627
|
+
L.restoreTries[id] = tries;
|
|
13628
|
+
if (tries >= 3) {
|
|
13629
|
+
delete L.restoreTries[id];
|
|
13630
|
+
// Three waits for a slot and none came: try again later, said to the client.
|
|
13631
|
+
__sbWsOp("c", id, "", 1013);
|
|
13632
|
+
__sbWsOp("r", id, "", 0);
|
|
13633
|
+
return;
|
|
13634
|
+
}
|
|
13635
|
+
// Back to the host's restore list with its queued events: the next slot serves it.
|
|
13636
|
+
__sbWsOp("g", id, "", 0);
|
|
13637
|
+
}
|
|
13638
|
+
|
|
12819
13639
|
function __sbDOStorage(cls, id) {
|
|
12820
13640
|
return {
|
|
12821
13641
|
get(key) {
|
|
@@ -12913,7 +13733,7 @@ function __sbDOPump(gate) {
|
|
|
12913
13733
|
// Shut the gate until the next timer turn (see above).
|
|
12914
13734
|
function __sbDOHold(gate) {
|
|
12915
13735
|
gate.locks++;
|
|
12916
|
-
|
|
13736
|
+
__sbSetTimeout(function () {
|
|
12917
13737
|
gate.locks--;
|
|
12918
13738
|
__sbDOPump(gate);
|
|
12919
13739
|
}, 0);
|
|
@@ -12949,6 +13769,12 @@ function __sbDOEnter(gate, run) {
|
|
|
12949
13769
|
// (kept on the instance, as __sbAlarmAt and __sbAlarmFails)
|
|
12950
13770
|
function __sbDOAlarmCall(inst, cls, id, at, attempts) {
|
|
12951
13771
|
if (__sbCompatDate() < "2026-11-04") return inst.alarm();
|
|
13772
|
+
// From the eviction date the count is the delivery row's (or the host's), never the instance's: the instance can be gone
|
|
13773
|
+
// between two runs of the same alarm.
|
|
13774
|
+
if (__sbDOEvictOn()) {
|
|
13775
|
+
const retries = attempts != null && Number(attempts) > 0 ? Number(attempts) : 0;
|
|
13776
|
+
return __sbDOAlarmRun(inst, { retryCount: retries, isRetry: retries > 0 });
|
|
13777
|
+
}
|
|
12952
13778
|
const when = Number(at) || 0;
|
|
12953
13779
|
if (inst.__sbAlarmAt !== when) {
|
|
12954
13780
|
inst.__sbAlarmAt = when;
|
|
@@ -13259,7 +14085,8 @@ function __sbDOStrictStorage(cls, id) {
|
|
|
13259
14085
|
setAlarm(when) {
|
|
13260
14086
|
const at = when instanceof Date ? when.getTime() : when;
|
|
13261
14087
|
if (__sbDOv3()) {
|
|
13262
|
-
const
|
|
14088
|
+
const live = __sbDOL !== null ? __sbDOL.recs[cls + " " + id] : undefined;
|
|
14089
|
+
const inst = live !== undefined ? live.inst : __sbDOInstances[cls + " " + id];
|
|
13263
14090
|
if (inst && !__sbIsFn(inst.alarm))
|
|
13264
14091
|
throw new TypeError("Your Durable Object class must have an alarm() handler in order to call setAlarm()");
|
|
13265
14092
|
if (when instanceof Date || Object(when) instanceof Number) {
|
|
@@ -13301,9 +14128,11 @@ function __sbDOStrictStorage(cls, id) {
|
|
|
13301
14128
|
}
|
|
13302
14129
|
|
|
13303
14130
|
// The 2026-10-28 storage: every method returns a Promise; reads shut the input gate.
|
|
13304
|
-
function __sbDOAsyncStorage(cls, id, gate) {
|
|
14131
|
+
function __sbDOAsyncStorage(cls, id, gate, rec) {
|
|
13305
14132
|
const s = __sbDOStrictStorage(cls, id);
|
|
14133
|
+
// rec is the record of an object that may be evicted (2026-11-21): once it is dead every storage call fails with the evicted error.
|
|
13306
14134
|
const settle = (f) => {
|
|
14135
|
+
if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
|
|
13307
14136
|
try {
|
|
13308
14137
|
return Promise.resolve(f());
|
|
13309
14138
|
} catch (error) {
|
|
@@ -13311,9 +14140,13 @@ function __sbDOAsyncStorage(cls, id, gate) {
|
|
|
13311
14140
|
}
|
|
13312
14141
|
};
|
|
13313
14142
|
const read = (f) => {
|
|
14143
|
+
if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
|
|
13314
14144
|
__sbDOHold(gate);
|
|
13315
14145
|
return settle(f);
|
|
13316
14146
|
};
|
|
14147
|
+
const alive = () => {
|
|
14148
|
+
if (rec !== undefined && rec.dead) throw __sbDOZombie(rec);
|
|
14149
|
+
};
|
|
13317
14150
|
const storage = {
|
|
13318
14151
|
get: (key) => read(() => s.get(key)),
|
|
13319
14152
|
list: (options) => read(() => s.list(options)),
|
|
@@ -13324,10 +14157,13 @@ function __sbDOAsyncStorage(cls, id, gate) {
|
|
|
13324
14157
|
setAlarm: (when) => settle(() => s.setAlarm(when)),
|
|
13325
14158
|
deleteAlarm: () => settle(() => s.deleteAlarm()),
|
|
13326
14159
|
sync: () => Promise.resolve(),
|
|
13327
|
-
transaction: (fn) =>
|
|
14160
|
+
transaction: (fn) => {
|
|
14161
|
+
if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
|
|
14162
|
+
return __sbDOTransaction(cls, id, s, gate, fn);
|
|
14163
|
+
},
|
|
13328
14164
|
};
|
|
13329
14165
|
if (__sbDOv2()) {
|
|
13330
|
-
const sql = __sbDOSql(cls, id);
|
|
14166
|
+
const sql = __sbDOSql(cls, id, rec);
|
|
13331
14167
|
storage.sql = sql;
|
|
13332
14168
|
storage.transactionSync = (fn) => sql.__sbTransaction(fn);
|
|
13333
14169
|
if (__sbDOv3()) {
|
|
@@ -13339,12 +14175,22 @@ function __sbDOAsyncStorage(cls, id, gate) {
|
|
|
13339
14175
|
});
|
|
13340
14176
|
// The synchronous key-value API, over the same keys as get() and put().
|
|
13341
14177
|
storage.kv = {
|
|
13342
|
-
get: (key) =>
|
|
14178
|
+
get: (key) => {
|
|
14179
|
+
alive();
|
|
14180
|
+
return s.get(key);
|
|
14181
|
+
},
|
|
13343
14182
|
put: (key, value) => {
|
|
14183
|
+
alive();
|
|
13344
14184
|
s.put(key, value);
|
|
13345
14185
|
},
|
|
13346
|
-
delete: (key) =>
|
|
13347
|
-
|
|
14186
|
+
delete: (key) => {
|
|
14187
|
+
alive();
|
|
14188
|
+
return s.delete(key);
|
|
14189
|
+
},
|
|
14190
|
+
list: (options) => {
|
|
14191
|
+
alive();
|
|
14192
|
+
return s.list(options).entries();
|
|
14193
|
+
},
|
|
13348
14194
|
};
|
|
13349
14195
|
// Point-in-time recovery needs the storage engine's log, which this one does not keep.
|
|
13350
14196
|
const pitr = () => Promise.reject(new Error("Point-in-time recovery is not supported by this Durable Object storage."));
|
|
@@ -13519,9 +14365,14 @@ function __sbDOSqlPolicy(text) {
|
|
|
13519
14365
|
}
|
|
13520
14366
|
}
|
|
13521
14367
|
|
|
13522
|
-
function __sbDOSql(cls, id) {
|
|
14368
|
+
function __sbDOSql(cls, id, rec) {
|
|
13523
14369
|
const file = __sbDOIsHex64(id) ? id.toLowerCase() : __sbDOSha("sbdo-file:" + id);
|
|
14370
|
+
if (rec !== undefined) rec.sqlFile = file;
|
|
13524
14371
|
const run = (sql, params) => {
|
|
14372
|
+
if (rec !== undefined) {
|
|
14373
|
+
if (rec.dead) throw __sbDOZombie(rec);
|
|
14374
|
+
rec.usedSql = true;
|
|
14375
|
+
}
|
|
13525
14376
|
try {
|
|
13526
14377
|
return __sbRpc("do.sql", { cls, file, sql, params });
|
|
13527
14378
|
} catch (err) {
|
|
@@ -14074,10 +14925,10 @@ async function __sbDrainWaitUntil(tasks, logRec) {
|
|
|
14074
14925
|
// busy for the full 25 s (sproutd will not reap a worker with timers left, B22).
|
|
14075
14926
|
let timer;
|
|
14076
14927
|
const cap = new Promise((resolve) => {
|
|
14077
|
-
timer =
|
|
14928
|
+
timer = __sbSetTimeout(resolve, __SB_WAITUNTIL_TIMEOUT_MS);
|
|
14078
14929
|
});
|
|
14079
14930
|
await Promise.race([__sbDrainAll(pending, logRec), cap]);
|
|
14080
|
-
|
|
14931
|
+
__sbClearTimeout(timer);
|
|
14081
14932
|
}
|
|
14082
14933
|
|
|
14083
14934
|
async function __sbDrainAll(pending, logRec) {
|
|
@@ -14199,7 +15050,7 @@ const __SB_STREAM_WINDOW = 1024 * 1024;
|
|
|
14199
15050
|
// Resolves when the host says the stream's unsent bytes fell (or the client went away).
|
|
14200
15051
|
function __sbStreamDrained(id) {
|
|
14201
15052
|
return new Promise((resolve) => {
|
|
14202
|
-
const timer =
|
|
15053
|
+
const timer = __sbSetTimeout(resolve, 2147483647);
|
|
14203
15054
|
__sbStreamOp("k", id, timer);
|
|
14204
15055
|
});
|
|
14205
15056
|
}
|
|
@@ -14210,6 +15061,14 @@ function __sbStreamResponse(response) {
|
|
|
14210
15061
|
response.headers._immutable = false;
|
|
14211
15062
|
response.headers.set("x-sb-stream", id);
|
|
14212
15063
|
response.body = "";
|
|
15064
|
+
// A streamed response of a Durable Object keeps its object busy until the stream ends (this runs in the object's frame).
|
|
15065
|
+
if (__sbDOL !== null) {
|
|
15066
|
+
const owner = __sbDOFrameRec();
|
|
15067
|
+
if (owner !== undefined && !owner.dead) {
|
|
15068
|
+
__sbDOPin(owner);
|
|
15069
|
+
response._sbDOPin = owner;
|
|
15070
|
+
}
|
|
15071
|
+
}
|
|
14213
15072
|
(async () => {
|
|
14214
15073
|
const reader = stream.getReader();
|
|
14215
15074
|
try {
|
|
@@ -14244,6 +15103,7 @@ function __sbStreamResponse(response) {
|
|
|
14244
15103
|
__sbLogUncaught("fetch", error);
|
|
14245
15104
|
}
|
|
14246
15105
|
__sbStreamOp("e", id, "");
|
|
15106
|
+
if (response._sbDOPin !== undefined) __sbDOUnpin(response._sbDOPin);
|
|
14247
15107
|
})();
|
|
14248
15108
|
return response;
|
|
14249
15109
|
}
|
|
@@ -14386,6 +15246,10 @@ class __SbWebSocket {
|
|
|
14386
15246
|
if (this._hib !== null) throw new TypeError("Can't accept() a WebSocket that was accepted with acceptWebSocket().");
|
|
14387
15247
|
if (this._accepted) return;
|
|
14388
15248
|
this._accepted = true;
|
|
15249
|
+
if (__sbDOL !== null) {
|
|
15250
|
+
const rec = __sbDOFrameRec();
|
|
15251
|
+
if (rec !== undefined && !rec.dead) rec.plain.push(this);
|
|
15252
|
+
}
|
|
14389
15253
|
__sbWsSchedule(this);
|
|
14390
15254
|
}
|
|
14391
15255
|
send(message) {
|
|
@@ -14557,6 +15421,16 @@ function __sbWsDispatch(ws, event) {
|
|
|
14557
15421
|
|
|
14558
15422
|
// A hibernatable socket's event runs the object's handler through its input gate.
|
|
14559
15423
|
function __sbWsHibernated(ws, name, args) {
|
|
15424
|
+
const rec = ws._hib.holder.rec;
|
|
15425
|
+
if (rec !== undefined && rec !== null) {
|
|
15426
|
+
if (rec.dead || rec.inst === null || !__sbIsFn(rec.inst[name])) return;
|
|
15427
|
+
__sbDOStart(
|
|
15428
|
+
rec,
|
|
15429
|
+
(inst) => (name === "webSocketClose" ? inst.webSocketClose(args[0], args[1], args[2], args[3]) : inst.webSocketMessage(args[0], args[1])),
|
|
15430
|
+
false,
|
|
15431
|
+
).then(undefined, (error) => __sbLogUncaught("websocket", error));
|
|
15432
|
+
return;
|
|
15433
|
+
}
|
|
14560
15434
|
const inst = ws._hib.holder.inst;
|
|
14561
15435
|
if (inst == null || !__sbIsFn(inst[name])) return;
|
|
14562
15436
|
const run = () => {
|
|
@@ -14577,12 +15451,14 @@ function __sbWsHibernated(ws, name, args) {
|
|
|
14577
15451
|
// state from 2026-10-30. `holder.inst` is the object once it exists.
|
|
14578
15452
|
function __sbDOWebSocketState(state, holder) {
|
|
14579
15453
|
const sockets = [];
|
|
15454
|
+
holder.sockets = sockets;
|
|
14580
15455
|
const live = () => {
|
|
14581
15456
|
for (let i = sockets.length - 1; i >= 0; i--) if (sockets[i]._state === __SB_WS_CLOSED) sockets.splice(i, 1);
|
|
14582
15457
|
return sockets;
|
|
14583
15458
|
};
|
|
14584
15459
|
__sbWsHibRestore(holder, sockets);
|
|
14585
15460
|
state.acceptWebSocket = function (ws, tags) {
|
|
15461
|
+
if (holder.rec !== undefined && holder.rec !== null && holder.rec.dead) throw __sbDOZombie(holder.rec);
|
|
14586
15462
|
if (!(ws instanceof __SbWebSocket)) throw new TypeError("acceptWebSocket needs a WebSocket");
|
|
14587
15463
|
if (ws._accepted || ws._hib !== null || ws._host !== 0) throw new TypeError("This WebSocket was already accepted.");
|
|
14588
15464
|
const list = [];
|
|
@@ -14657,7 +15533,8 @@ function __sbWsPump(ws) {
|
|
|
14657
15533
|
for (;;) {
|
|
14658
15534
|
const head = __sbWsOp("p", id, "", 0);
|
|
14659
15535
|
if (head === "") {
|
|
14660
|
-
const timer =
|
|
15536
|
+
const timer = __sbSetTimeout(() => __sbWsPump(ws), 2147483647);
|
|
15537
|
+
ws._pumpTimer = timer;
|
|
14661
15538
|
// From 2026-11-01 a parked socket is not background work (z): its app may be reaped.
|
|
14662
15539
|
__sbWsOp(__sbWsHibOn() ? "z" : "w", id, "", timer);
|
|
14663
15540
|
return;
|
|
@@ -14746,9 +15623,9 @@ function __sbTcpPump(st) {
|
|
|
14746
15623
|
if (head === "") {
|
|
14747
15624
|
if (st.id === 0) return;
|
|
14748
15625
|
if (__sbWsCanWake()) {
|
|
14749
|
-
st.timer =
|
|
15626
|
+
st.timer = __sbSetTimeout(() => __sbTcpPump(st), 2147483647);
|
|
14750
15627
|
__sbWsOp("w", st.id, "", st.timer);
|
|
14751
|
-
} else st.timer =
|
|
15628
|
+
} else st.timer = __sbSetTimeout(() => __sbTcpPump(st), 10);
|
|
14752
15629
|
return;
|
|
14753
15630
|
}
|
|
14754
15631
|
const parts = head.split(" ");
|
|
@@ -14813,7 +15690,7 @@ function __sbTcpRelease(st) {
|
|
|
14813
15690
|
const id = st.id;
|
|
14814
15691
|
if (id === 0) return;
|
|
14815
15692
|
st.id = 0;
|
|
14816
|
-
if (st.timer !== undefined)
|
|
15693
|
+
if (st.timer !== undefined) __sbClearTimeout(st.timer);
|
|
14817
15694
|
__sbWsOp("r", id, "", 0);
|
|
14818
15695
|
__sbTcpOpen--;
|
|
14819
15696
|
}
|
|
@@ -14961,7 +15838,7 @@ function __sbTcpFacade(st, mode, upgrade) {
|
|
|
14961
15838
|
// the old socket ends once the upgrade is under way (workerd resolves its `closed`)
|
|
14962
15839
|
const oldResolve = st.closedResolve;
|
|
14963
15840
|
const next = __sbTcpFacade(st, "on", upgrade);
|
|
14964
|
-
|
|
15841
|
+
__sbSetTimeout(() => oldResolve(undefined), 0);
|
|
14965
15842
|
return next;
|
|
14966
15843
|
},
|
|
14967
15844
|
};
|
|
@@ -14993,7 +15870,7 @@ function __sbConnect(address, options) {
|
|
|
14993
15870
|
if (target.port === 25) {
|
|
14994
15871
|
st.failed = new Error("proxy request failed, cannot connect to the specified address");
|
|
14995
15872
|
st.eof = true;
|
|
14996
|
-
|
|
15873
|
+
__sbSetTimeout(() => __sbTcpSettle(st), 0);
|
|
14997
15874
|
return socket;
|
|
14998
15875
|
}
|
|
14999
15876
|
const spec = target.host + "\n" + target.port + "\n" + (mode === "on" ? "1" : mode === "starttls" ? "2" : "0") + "\n" + target.host;
|
|
@@ -15001,11 +15878,15 @@ function __sbConnect(address, options) {
|
|
|
15001
15878
|
if (!(id > 0)) {
|
|
15002
15879
|
st.failed = new Error(__sbTcpOpen >= 16 ? "Too many open connections" : "TCP sockets are not available on this host");
|
|
15003
15880
|
st.eof = true;
|
|
15004
|
-
|
|
15881
|
+
__sbSetTimeout(() => __sbTcpSettle(st), 0);
|
|
15005
15882
|
return socket;
|
|
15006
15883
|
}
|
|
15007
15884
|
st.id = id;
|
|
15008
15885
|
__sbTcpOpen++;
|
|
15886
|
+
if (__sbDOL !== null) {
|
|
15887
|
+
const rec = __sbDOFrameRec();
|
|
15888
|
+
if (rec !== undefined && !rec.dead) rec.tcp.push(st);
|
|
15889
|
+
}
|
|
15009
15890
|
__sbTcpPump(st);
|
|
15010
15891
|
return socket;
|
|
15011
15892
|
}
|
|
@@ -15082,10 +15963,14 @@ function __sbWsConnectOutbound(ws, input, protocols) {
|
|
|
15082
15963
|
ws._accepted = true;
|
|
15083
15964
|
ws._state = 0;
|
|
15084
15965
|
ws.url = target.url;
|
|
15966
|
+
if (__sbDOL !== null) {
|
|
15967
|
+
const rec = __sbDOFrameRec();
|
|
15968
|
+
if (rec !== undefined && !rec.dead) rec.plain.push(ws);
|
|
15969
|
+
}
|
|
15085
15970
|
const id = __sbWsOpen(target, list, null);
|
|
15086
15971
|
if (!(id > 0)) {
|
|
15087
15972
|
ws._state = __SB_WS_CLOSED;
|
|
15088
|
-
|
|
15973
|
+
__sbSetTimeout(() => {
|
|
15089
15974
|
__sbWsFire(ws, { type: "error", target: ws });
|
|
15090
15975
|
__sbWsFire(ws, { type: "close", code: 1006, reason: "", wasClean: false, target: ws });
|
|
15091
15976
|
}, 0);
|
|
@@ -15104,9 +15989,9 @@ function __sbWsPumpOut(ws) {
|
|
|
15104
15989
|
const head = __sbWsOp("p", id, "", 0);
|
|
15105
15990
|
if (head === "") {
|
|
15106
15991
|
if (__sbWsCanWake()) {
|
|
15107
|
-
const timer =
|
|
15992
|
+
const timer = __sbSetTimeout(() => __sbWsPumpOut(ws), 2147483647);
|
|
15108
15993
|
__sbWsOp("w", id, "", timer);
|
|
15109
|
-
} else
|
|
15994
|
+
} else __sbSetTimeout(() => __sbWsPumpOut(ws), 15);
|
|
15110
15995
|
return;
|
|
15111
15996
|
}
|
|
15112
15997
|
const parts = head.split(" ");
|
|
@@ -15212,7 +16097,7 @@ function __sbWsOutClose(ws, code, text) {
|
|
|
15212
16097
|
const pending = ws._pending;
|
|
15213
16098
|
ws._pending = null;
|
|
15214
16099
|
if (pending) pending.reject(new TypeError("WebSocket connection was closed before it opened"));
|
|
15215
|
-
else
|
|
16100
|
+
else __sbSetTimeout(() => {
|
|
15216
16101
|
__sbWsFire(ws, { type: "error", target: ws });
|
|
15217
16102
|
__sbWsFire(ws, { type: "close", code: 1006, reason: "", wasClean: false, target: ws });
|
|
15218
16103
|
}, 0);
|
|
@@ -15221,7 +16106,7 @@ function __sbWsOutClose(ws, code, text) {
|
|
|
15221
16106
|
ws._state = __SB_WS_CLOSING;
|
|
15222
16107
|
__sbWsOp("c", id, text, code === undefined ? 1000 : code);
|
|
15223
16108
|
// A peer that never answers the close is dropped after a while.
|
|
15224
|
-
|
|
16109
|
+
__sbSetTimeout(() => {
|
|
15225
16110
|
if (ws._host !== id) return;
|
|
15226
16111
|
ws._host = 0;
|
|
15227
16112
|
__sbWsOp("r", id, "", 0);
|
|
@@ -15414,8 +16299,7 @@ function __sbWsRestoreLoop() {
|
|
|
15414
16299
|
__sbLogUncaught("websocket", error);
|
|
15415
16300
|
}
|
|
15416
16301
|
}
|
|
15417
|
-
|
|
15418
|
-
__sbWsOp("R", 0, "", timer);
|
|
16302
|
+
__sbWsRestorePark();
|
|
15419
16303
|
}
|
|
15420
16304
|
function __sbWsRestoreSocket(id) {
|
|
15421
16305
|
const r = __sbRpc("do.storage.get", { cls: "__sbws", id: "index", key: "s" + id });
|
|
@@ -15434,6 +16318,10 @@ function __sbWsRestoreSocket(id) {
|
|
|
15434
16318
|
return;
|
|
15435
16319
|
}
|
|
15436
16320
|
// Constructing the object restores its sockets (__sbWsHibRestore) and starts their pumps.
|
|
16321
|
+
if (__sbDOEvictOn()) {
|
|
16322
|
+
__sbDORun(target[0], target[1], undefined, () => undefined, false).then(undefined, (error) => __sbWsRestoreFailed(id, error));
|
|
16323
|
+
return;
|
|
16324
|
+
}
|
|
15437
16325
|
__sbGetDOInstance(target[0], target[1]);
|
|
15438
16326
|
}
|
|
15439
16327
|
|
|
@@ -15869,7 +16757,7 @@ function __sbIngressStream(request, id) {
|
|
|
15869
16757
|
const state = Number(__sbReqBodyOp("p", id, ""));
|
|
15870
16758
|
if (state === 0) {
|
|
15871
16759
|
// Nothing yet: the server fires this timer when bytes, the end or an error arrive.
|
|
15872
|
-
const timer =
|
|
16760
|
+
const timer = __sbSetTimeout(() => step(controller, resolve), 2147483647);
|
|
15873
16761
|
__sbReqBodyOp("w", id, timer);
|
|
15874
16762
|
return;
|
|
15875
16763
|
}
|
|
@@ -16015,7 +16903,7 @@ function __sbR2TransferFailed(status, text) {
|
|
|
16015
16903
|
// Resolves when the streamed request body `body` has more (or ended or failed): the host fires the parked timer.
|
|
16016
16904
|
function __sbR2BodyWait(body) {
|
|
16017
16905
|
return new Promise((resolve) => {
|
|
16018
|
-
const timer =
|
|
16906
|
+
const timer = __sbSetTimeout(resolve, 2147483647);
|
|
16019
16907
|
__sbReqBodyOp("w", body, timer);
|
|
16020
16908
|
});
|
|
16021
16909
|
}
|
|
@@ -16267,6 +17155,22 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
16267
17155
|
const body = __sbReadJson(request);
|
|
16268
17156
|
const __acls = String(body.cls || "");
|
|
16269
17157
|
const __aid = String(body.id || "");
|
|
17158
|
+
if (__sbDOEvictOn()) {
|
|
17159
|
+
const none = {};
|
|
17160
|
+
return __sbDORun(
|
|
17161
|
+
__acls,
|
|
17162
|
+
__aid,
|
|
17163
|
+
undefined,
|
|
17164
|
+
(instance) => (__sbIsFn(instance.alarm) ? __sbDOAlarmCall(instance, __acls, __aid, body.scheduledTime, body.attempts) : none),
|
|
17165
|
+
true,
|
|
17166
|
+
).then(
|
|
17167
|
+
(value) => (value === none ? new Response("no alarm handler", { status: 404 }) : new Response(null, { status: 204 })),
|
|
17168
|
+
(error) => {
|
|
17169
|
+
__sbLogUncaught("alarm", error);
|
|
17170
|
+
return error && error.overloaded === true ? new Response("alarm deferred: too many objects are awake", { status: 503 }) : new Response("alarm failed", { status: 500 });
|
|
17171
|
+
},
|
|
17172
|
+
);
|
|
17173
|
+
}
|
|
16270
17174
|
const inst = __sbGetDOInstance(__acls, __aid);
|
|
16271
17175
|
if (!__sbIsFn(inst.alarm)) return new Response("no alarm handler", { status: 404 });
|
|
16272
17176
|
const __retries = __sbCompatDate() >= "2026-11-04";
|