@feng0531/dsh-execution-bus 0.12.1 → 1.0.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.
Files changed (44) hide show
  1. package/README.md +47 -264
  2. package/cordis.patch.yml +1 -1
  3. package/dist/error-codes.d.ts +14 -0
  4. package/dist/error-codes.js +15 -0
  5. package/dist/mode.d.ts +2 -2
  6. package/dist/mode.js +7 -7
  7. package/dist/plugin.d.ts +2 -6
  8. package/dist/plugin.js +28 -232
  9. package/dist/preset.d.ts +1 -1
  10. package/dist/preset.js +4 -6
  11. package/dist/product.d.ts +91 -195
  12. package/dist/product.js +66 -146
  13. package/dist/runtime/executor.d.ts +18 -0
  14. package/dist/runtime/executor.js +239 -0
  15. package/dist/runtime/plan.d.ts +30 -0
  16. package/dist/runtime/plan.js +185 -0
  17. package/dist/runtime/service.d.ts +66 -0
  18. package/dist/runtime/service.js +185 -0
  19. package/dist/runtime/store.d.ts +45 -0
  20. package/dist/runtime/store.js +102 -0
  21. package/docs/DECLARATIVE-RUNTIME.md +44 -0
  22. package/docs/FLOW-TEST.md +7 -54
  23. package/docs/INTEGRATION.md +3 -1
  24. package/docs/PRODUCT.md +15 -42
  25. package/docs/RETROSPECTIVE.md +51 -0
  26. package/docs/host-compatibility.md +11 -74
  27. package/docs/protocol.md +27 -135
  28. package/docs/releases/1.0.0.md +33 -0
  29. package/docs/validation.md +23 -62
  30. package/package.json +4 -6
  31. package/dist/bus.d.ts +0 -153
  32. package/dist/bus.js +0 -833
  33. package/dist/diagnostics.d.ts +0 -41
  34. package/dist/diagnostics.js +0 -32
  35. package/dist/execution.d.ts +0 -70
  36. package/dist/execution.js +0 -193
  37. package/dist/handlers.d.ts +0 -27
  38. package/dist/handlers.js +0 -194
  39. package/dist/json.d.ts +0 -4
  40. package/dist/json.js +0 -46
  41. package/dist/limits.d.ts +0 -20
  42. package/dist/limits.js +0 -23
  43. package/dist/protocol.d.ts +0 -62
  44. package/dist/protocol.js +0 -54
package/dist/bus.js DELETED
@@ -1,833 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { performance } from "node:perf_hooks";
3
- import { Diagnostics } from "./diagnostics.js";
4
- import { ERRORS } from "./error-codes.js";
5
- import { assertJson } from "./json.js";
6
- import { LIMITS } from "./limits.js";
7
- import { CORE_DEFAULTS, SCHEDULER_POLICY } from "./product.js";
8
- import { canonical, freezeJson, object, parse } from "./protocol.js";
9
- export { object, parse };
10
- const isTerminal = (s) => !["queued", "running"].includes(s);
11
- /** 对 payload 做规范排序后的 SHA-256 摘要,仅用于同 requestId 的冲突比较。 */
12
- function hashPayload(payload) {
13
- return createHash("sha256").update(canonical(payload)).digest("hex");
14
- }
15
- /** 消息身份只保存摘要,避免为每个 messageId 保留完整消息文本占用内存。 */
16
- function hashMessage(m) {
17
- return createHash("sha256").update(canonical(m)).digest("hex");
18
- }
19
- /** Volatile single-process bus; no acceptance durability or automatic execution retry. */
20
- export class ExecutionBus {
21
- options;
22
- admission;
23
- scheduler;
24
- entries = new Map();
25
- handlers = new Map();
26
- queue = [];
27
- readyOwners = [];
28
- running = 0;
29
- runningByHandler = new Map();
30
- /** 资源键 → 占用它的运行中 Entry;有副作用 Handler 据此互斥。 */
31
- resourceLocks = new Map();
32
- /** payload 指纹 → 已成功或运行中的 Entry,用于唯一任务复用。 */
33
- dedupeIndex = new Map();
34
- retainedResultBytes = 0;
35
- closed = false;
36
- telemetry = new Diagnostics();
37
- diagnostics() {
38
- return {
39
- ...this.telemetry.snapshot(),
40
- phase: this.shutdownPhase,
41
- queued: this.queue.length,
42
- running: this.running,
43
- records: this.entries.size,
44
- retainedResultBytes: this.retainedResultBytes,
45
- };
46
- }
47
- closing;
48
- shutdownPhase = "open";
49
- constructor(options = CORE_DEFAULTS, admission = {}, scheduler = SCHEDULER_POLICY) {
50
- this.options = options;
51
- this.admission = admission;
52
- this.scheduler = scheduler;
53
- const schedulerKeys = ["queueClassOrder", "queueAgingMs", "longRunningMs"];
54
- if (!scheduler ||
55
- typeof scheduler !== "object" ||
56
- Array.isArray(scheduler) ||
57
- Reflect.ownKeys(scheduler).some((key) => typeof key !== "string" || !schedulerKeys.includes(key)))
58
- throw Error(ERRORS.INVALID_OPTIONS);
59
- for (const key of Reflect.ownKeys(scheduler)) {
60
- const descriptor = Object.getOwnPropertyDescriptor(scheduler, key);
61
- if (!("value" in descriptor) || !descriptor.enumerable)
62
- throw Error(ERRORS.INVALID_OPTIONS);
63
- }
64
- // 只读取一次:拒绝 accessor,避免校验值与落库值不一致。
65
- const queueAgingMs = scheduler.queueAgingMs;
66
- const longRunningMs = scheduler.longRunningMs;
67
- const rawOrder = scheduler.queueClassOrder;
68
- if (!Number.isSafeInteger(queueAgingMs) ||
69
- queueAgingMs < 1 ||
70
- !Number.isSafeInteger(longRunningMs) ||
71
- longRunningMs < 1 ||
72
- !rawOrder ||
73
- typeof rawOrder !== "object" ||
74
- Array.isArray(rawOrder))
75
- throw Error(ERRORS.INVALID_OPTIONS);
76
- const order = Object.create(null);
77
- for (const className of Reflect.ownKeys(rawOrder)) {
78
- if (typeof className !== "string")
79
- throw Error(ERRORS.INVALID_OPTIONS);
80
- if (["__proto__", "constructor", "prototype"].includes(className))
81
- throw Error(ERRORS.INVALID_OPTIONS);
82
- const descriptor = Object.getOwnPropertyDescriptor(rawOrder, className);
83
- if (!("value" in descriptor) || !descriptor.enumerable)
84
- throw Error(ERRORS.INVALID_OPTIONS);
85
- const rank = descriptor.value;
86
- if (className.length === 0 ||
87
- className.length > 64 ||
88
- !Number.isSafeInteger(rank) ||
89
- rank < 0)
90
- throw Error(ERRORS.INVALID_OPTIONS);
91
- order[className] = rank;
92
- }
93
- this.scheduler = Object.freeze({
94
- queueClassOrder: Object.freeze(order),
95
- queueAgingMs: queueAgingMs,
96
- longRunningMs: longRunningMs,
97
- });
98
- const allowed = ["sessionQueueLimit", "maxQueueWaitMs"];
99
- if (!admission ||
100
- typeof admission !== "object" ||
101
- Array.isArray(admission) ||
102
- Reflect.ownKeys(admission).some((key) => typeof key !== "string" || !allowed.includes(key)))
103
- throw Error(ERRORS.INVALID_ADMISSION_POLICY);
104
- for (const key of Reflect.ownKeys(admission)) {
105
- const descriptor = Object.getOwnPropertyDescriptor(admission, key);
106
- if (!("value" in descriptor) ||
107
- !descriptor.enumerable ||
108
- !Number.isSafeInteger(descriptor.value) ||
109
- descriptor.value < 1 ||
110
- descriptor.value > 2147483647)
111
- throw Error(ERRORS.INVALID_ADMISSION_POLICY);
112
- }
113
- this.admission = Object.freeze({ ...admission });
114
- const keys = [
115
- "concurrency",
116
- "queueLimit",
117
- "recordLimit",
118
- "resultBytes",
119
- "retentionMs",
120
- ];
121
- if (!options ||
122
- typeof options !== "object" ||
123
- Array.isArray(options) ||
124
- Reflect.ownKeys(options).length !== keys.length ||
125
- keys.some((key) => {
126
- const descriptor = Object.getOwnPropertyDescriptor(options, key);
127
- return (!descriptor || !("value" in descriptor) || !descriptor.enumerable);
128
- }))
129
- throw Error(ERRORS.INVALID_OPTIONS);
130
- this.options = Object.freeze({ ...options });
131
- for (const v of Object.values(this.options))
132
- if (!Number.isSafeInteger(v) || v < 1)
133
- throw Error(ERRORS.INVALID_OPTIONS);
134
- }
135
- register(name, handler) {
136
- if (this.handlers.has(name))
137
- throw Error(ERRORS.DUPLICATE_HANDLER);
138
- if (!handler ||
139
- typeof handler !== "object" ||
140
- typeof handler.validate !== "function" ||
141
- typeof handler.run !== "function" ||
142
- (handler.idempotent !== undefined && typeof handler.idempotent !== "boolean") ||
143
- (handler.queue !== undefined &&
144
- (typeof handler.queue !== "string" ||
145
- handler.queue.length === 0 ||
146
- handler.queue.length > 64 ||
147
- ["__proto__", "constructor", "prototype"].includes(handler.queue))) ||
148
- (handler.maxConcurrency !== undefined &&
149
- (!Number.isSafeInteger(handler.maxConcurrency) ||
150
- handler.maxConcurrency < 1)) ||
151
- (handler.dedupe !== undefined && handler.dedupe !== "payload") ||
152
- (handler.dedupe === "payload" && handler.idempotent !== true) ||
153
- (handler.resources !== undefined && typeof handler.resources !== "function"))
154
- throw Error(ERRORS.INVALID_HANDLER_OPTIONS);
155
- // 注册时快照并冻结调度/复用选项,运行期修改原对象不再影响已注册行为。
156
- const fixed = Object.freeze({
157
- idempotent: handler.idempotent,
158
- queue: handler.queue,
159
- maxConcurrency: handler.maxConcurrency,
160
- dedupe: handler.dedupe,
161
- resources: handler.resources?.bind(handler),
162
- validate: handler.validate.bind(handler),
163
- run: handler.run.bind(handler),
164
- });
165
- const registration = { handler: fixed, token: Symbol(name) };
166
- this.handlers.set(name, registration);
167
- let disposed = false;
168
- return () => {
169
- if (disposed)
170
- return;
171
- disposed = true;
172
- if (this.handlers.get(name)?.token === registration.token)
173
- this.handlers.delete(name);
174
- };
175
- }
176
- key(o, id) {
177
- if (!o.sessionId || !o.callId)
178
- throw Error(ERRORS.UNTRUSTED_CONTEXT);
179
- return JSON.stringify([o.sessionId, id]);
180
- }
181
- emit(e, type, payload, correlationId = "") {
182
- e.view.sequence++;
183
- const event = freezeJson({
184
- protocolVersion: 1,
185
- messageId: randomUUID(),
186
- requestId: e.view.requestId,
187
- correlationId: correlationId || e.correlationId,
188
- sequence: e.view.sequence,
189
- type,
190
- payload,
191
- });
192
- // 在调用用户代码前快照并排队:嵌套取消无法抢到当前事件。
193
- e.notifications.push({ event, listeners: [...e.listeners] });
194
- if (e.dispatching)
195
- return;
196
- e.dispatching = true;
197
- try {
198
- while (e.notifications.length) {
199
- const batch = e.notifications.shift();
200
- for (const listener of batch.listeners) {
201
- if (!e.listeners.has(listener))
202
- continue;
203
- try {
204
- void Promise.resolve(listener(batch.event)).catch(() => { });
205
- }
206
- catch { }
207
- }
208
- }
209
- }
210
- finally {
211
- e.dispatching = false;
212
- if (isTerminal(e.view.state))
213
- e.listeners.clear();
214
- }
215
- }
216
- view(e) {
217
- return structuredClone(e.view);
218
- }
219
- get(o, id) {
220
- const e = this.entries.get(this.key(o, id));
221
- if (!e)
222
- throw Error(ERRORS.NOT_FOUND);
223
- return e;
224
- }
225
- sweep(now = Date.now()) {
226
- for (const [k, e] of this.entries)
227
- if (isTerminal(e.view.state) && now - e.created > this.options.retentionMs) {
228
- this.retainedResultBytes -= e.resultBytes;
229
- this.entries.delete(k);
230
- }
231
- const live = new Set(this.entries.values());
232
- for (const [key, e] of this.dedupeIndex)
233
- if (!live.has(e))
234
- this.dedupeIndex.delete(key);
235
- }
236
- submit(owner, raw, onEvent, invoke) {
237
- if (this.closed)
238
- throw Error(ERRORS.CLOSED);
239
- const m = parse(raw);
240
- if (m.type !== "request")
241
- throw Error(ERRORS.EXPECTED_REQUEST);
242
- const parts = this.requestParts(m);
243
- const key = this.key(owner, m.requestId);
244
- const fingerprint = hashPayload(parts.payload);
245
- this.sweep();
246
- const existing = this.entries.get(key);
247
- return existing
248
- ? this.reuseInvocation(existing, m, fingerprint, onEvent)
249
- : this.admitInvocation(owner, m, parts, key, fingerprint, invoke, onEvent);
250
- }
251
- /** 校验并拆解 request 的 payload,返回 handler 名、入参与原文。 */
252
- requestParts(m) {
253
- const p = m.payload;
254
- if (Object.keys(p).some((k) => !["handler", "input"].includes(k)) ||
255
- typeof p.handler !== "string")
256
- throw Error(ERRORS.INVALID_REQUEST);
257
- return {
258
- handler: p.handler,
259
- input: object(p.input),
260
- payload: p,
261
- };
262
- }
263
- /** 同一 requestId 的重复提交:校验一致性后复用执行或结果。 */
264
- reuseInvocation(e, m, fingerprint, onEvent) {
265
- if (e.fingerprint !== fingerprint)
266
- throw Error(ERRORS.REQUEST_CONFLICT);
267
- // 仅显式声明非幂等(idempotent:false)的 handler,终态结果才不可复用:
268
- // 返回"已执行"会掩盖副作用状态。运行中的重复 attach 仍放行,不会二次执行。
269
- if (e.handler.idempotent === false && isTerminal(e.view.state))
270
- throw Error(ERRORS.MUTATING_REUSE_DENIED);
271
- const previous = e.messages.get(m.messageId);
272
- if (previous && previous !== hashMessage(m))
273
- throw Error(ERRORS.MESSAGE_CONFLICT);
274
- if (!previous && e.messages.size >= LIMITS.messageAccountLimit)
275
- throw Error(ERRORS.MESSAGE_LIMIT);
276
- // 先取得附着,再提交消息身份:附着被拒(WAITER_LIMIT)时无副作用。
277
- const attached = this.attachment(e, onEvent, m.messageId, true);
278
- if (!previous)
279
- e.messages.set(m.messageId, hashMessage(m));
280
- this.telemetry.record(e.trace, "reused");
281
- return attached;
282
- }
283
- /** 受理全新请求:校验 Handler、检查容量、构建记录并入队。 */
284
- admitInvocation(owner, m, parts, key, fingerprint, invoke, onEvent) {
285
- const handler = this.handlers.get(parts.handler)?.handler;
286
- if (!handler)
287
- throw Error(ERRORS.UNKNOWN_HANDLER);
288
- handler.validate(parts.input);
289
- // validate 是用户代码:可能在同步执行中触发 close()。
290
- if (this.closed)
291
- throw Error(ERRORS.CLOSED);
292
- const dedupeKey = handler.dedupe === "payload"
293
- ? JSON.stringify([owner.sessionId, parts.handler, fingerprint])
294
- : undefined;
295
- if (dedupeKey !== undefined) {
296
- const target = this.dedupeIndex.get(dedupeKey);
297
- if (target !== undefined &&
298
- target.view.state === "succeeded" &&
299
- isTerminal(target.view.state)) {
300
- try {
301
- const unique = this.syntheticUnique(owner, m, parts, key, fingerprint, handler, target, dedupeKey);
302
- return this.attachment(unique, onEvent, m.messageId, true);
303
- }
304
- catch (error) {
305
- if (error instanceof Error && error.message === ERRORS.BACKPRESSURE)
306
- throw error;
307
- // 结果不可克隆时退回正常执行,避免把唯一缓存错误变成请求失败。
308
- }
309
- }
310
- }
311
- const resources = this.resourcesFor(handler, parts.input);
312
- // resources 也是用户代码,返回后重新确认总线未在同步段关闭。
313
- if (this.closed)
314
- throw Error(ERRORS.CLOSED);
315
- if (this.entries.size >= this.options.recordLimit ||
316
- this.queue.length >= this.options.queueLimit)
317
- throw Error(ERRORS.BACKPRESSURE);
318
- if (this.admission.sessionQueueLimit !== undefined &&
319
- this.queue.filter((entry) => entry.owner === owner.sessionId).length >=
320
- this.admission.sessionQueueLimit)
321
- throw Error(ERRORS.SESSION_BACKPRESSURE);
322
- let resolve;
323
- const done = new Promise((r) => (resolve = r));
324
- const e = {
325
- trace: this.telemetry.begin(),
326
- resultBytes: 0,
327
- invoke,
328
- correlationId: m.messageId,
329
- owner: owner.sessionId,
330
- fingerprint,
331
- handler,
332
- handlerName: parts.handler,
333
- queueClass: handler.queue ?? "default",
334
- resources,
335
- dedupeKey,
336
- input: structuredClone(parts.input),
337
- view: {
338
- requestId: m.requestId,
339
- state: "queued",
340
- sequence: 0,
341
- cancelRequested: false,
342
- delivery: "pending",
343
- },
344
- controller: new AbortController(),
345
- done,
346
- resolve,
347
- listeners: new Set(),
348
- notifications: [],
349
- dispatching: false,
350
- sourceSequence: 0,
351
- created: Date.now(),
352
- messages: new Map([[m.messageId, hashMessage(m)]]),
353
- };
354
- this.entries.set(key, e);
355
- if (dedupeKey !== undefined)
356
- this.dedupeIndex.set(dedupeKey, e);
357
- this.telemetry.record(e.trace, "accepted");
358
- const attached = this.attachment(e, onEvent, m.messageId);
359
- this.queue.push(e);
360
- this.scheduleQueueDeadline(e);
361
- if (!this.readyOwners.includes(e.owner))
362
- this.readyOwners.push(e.owner);
363
- queueMicrotask(() => this.pump());
364
- return attached;
365
- }
366
- /** 若启用了排队期限,登记到期结算为 QUEUE_WAIT_TIMEOUT 的计时器。 */
367
- scheduleQueueDeadline(e) {
368
- if (this.admission.maxQueueWaitMs === undefined)
369
- return;
370
- e.deadline = performance.now() + this.admission.maxQueueWaitMs;
371
- const expire = () => {
372
- if (e.view.state !== "queued")
373
- return;
374
- const remaining = e.deadline - performance.now();
375
- if (remaining > 0) {
376
- e.queueTimer = setTimeout(expire, Math.ceil(remaining));
377
- return;
378
- }
379
- this.removeQueued(e);
380
- this.finish(e, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
381
- };
382
- e.queueTimer = setTimeout(expire, this.admission.maxQueueWaitMs);
383
- }
384
- /** 计算并校验 Handler 的资源键;失败则请求不进入队列。 */
385
- resourcesFor(handler, input) {
386
- if (handler.resources === undefined)
387
- return [];
388
- let raw;
389
- try {
390
- raw = handler.resources(input);
391
- }
392
- catch {
393
- throw Error(ERRORS.INVALID_RESOURCES);
394
- }
395
- if (!Array.isArray(raw) || raw.length > 8)
396
- throw Error(ERRORS.INVALID_RESOURCES);
397
- const resources = [];
398
- for (const resource of raw) {
399
- if (typeof resource !== "string" ||
400
- resource.length === 0 ||
401
- resource.length > 256)
402
- throw Error(ERRORS.INVALID_RESOURCES);
403
- if (!resources.includes(resource))
404
- resources.push(resource);
405
- }
406
- resources.sort();
407
- return resources;
408
- }
409
- /** 唯一任务命中已成功终态时,构造一个不再执行 Handler 的只读结果记录。 */
410
- syntheticUnique(owner, m, parts, key, fingerprint, handler, target, dedupeKey) {
411
- if (this.entries.size >= this.options.recordLimit)
412
- throw Error(ERRORS.BACKPRESSURE);
413
- const snapshot = freezeJson(structuredClone({
414
- requestId: m.requestId,
415
- state: "succeeded",
416
- sequence: 1,
417
- cancelRequested: false,
418
- delivery: "pending",
419
- result: target.view.result,
420
- }));
421
- let resultBytes = 0;
422
- try {
423
- if (snapshot.result !== undefined)
424
- resultBytes = Buffer.byteLength(JSON.stringify(snapshot.result));
425
- }
426
- catch {
427
- resultBytes = target.resultBytes;
428
- }
429
- let resolve;
430
- const done = new Promise((r) => (resolve = r));
431
- const trace = this.telemetry.begin();
432
- const e = {
433
- trace,
434
- resultBytes,
435
- startedAt: trace.at,
436
- finishedAt: trace.at,
437
- invoke: undefined,
438
- correlationId: m.messageId,
439
- owner: owner.sessionId,
440
- fingerprint,
441
- handler,
442
- handlerName: parts.handler,
443
- queueClass: handler.queue ?? "default",
444
- resources: [],
445
- dedupeKey,
446
- input: structuredClone(parts.input),
447
- view: { ...snapshot, sequence: 1 },
448
- controller: new AbortController(),
449
- done,
450
- resolve,
451
- listeners: new Set(),
452
- notifications: [],
453
- dispatching: false,
454
- sourceSequence: 0,
455
- created: Date.now(),
456
- messages: new Map([[m.messageId, hashMessage(m)]]),
457
- };
458
- this.entries.set(key, e);
459
- this.retainedResultBytes += resultBytes;
460
- this.telemetry.record(e.trace, "accepted");
461
- this.telemetry.record(e.trace, "unique_reused");
462
- e.resolve(snapshot);
463
- return e;
464
- }
465
- releaseResources(e) {
466
- for (const resource of e.resources)
467
- if (this.resourceLocks.get(resource) === e)
468
- this.resourceLocks.delete(resource);
469
- }
470
- /** 优先类别表按自有属性查;未知类别回退 default 权重,避免原型链 NaN。 */
471
- classRank(queueClass) {
472
- const order = this.scheduler.queueClassOrder;
473
- return Object.hasOwn(order, queueClass) ? order[queueClass] : 3;
474
- }
475
- /** 当前阻塞来源:Handler 舱壁优先于资源锁;undefined 表示可运行。 */
476
- blockedBy(e) {
477
- const limit = e.handler.maxConcurrency;
478
- if (limit !== undefined &&
479
- (this.runningByHandler.get(e.handler) ?? 0) >= limit)
480
- return "bulkhead";
481
- for (const resource of e.resources)
482
- if (this.resourceLocks.has(resource))
483
- return "resource";
484
- return undefined;
485
- }
486
- /** 是否有空闲槽位供此 Entry 开始执行(Handler 舱壁 + 资源互斥)。 */
487
- eligible(e) {
488
- return this.blockedBy(e) === undefined;
489
- }
490
- /** 类别优先级 + 等待老化:等待越久提升越多(不封顶),最低到 0;
491
- * 同分时按队列先后(FIFO)决出,避免低优先类别饥饿。 */
492
- effectivePriority(e, now) {
493
- const boost = Math.floor(Math.max(0, now - e.trace.at) / this.scheduler.queueAgingMs);
494
- return Math.max(0, this.classRank(e.queueClass) - boost);
495
- }
496
- /** 对已到期的排队任务结算 QUEUE_WAIT_TIMEOUT,再从队列移除。 */
497
- expireQueued(now) {
498
- for (const e of [...this.queue]) {
499
- if (e.view.state !== "queued") {
500
- this.removeQueued(e);
501
- continue;
502
- }
503
- if (e.deadline !== undefined && now >= e.deadline) {
504
- this.removeQueued(e);
505
- this.finish(e, "failed", undefined, "QUEUE_WAIT_TIMEOUT");
506
- }
507
- }
508
- }
509
- attachment(e, onEvent, id, reused = false) {
510
- // 每次附着独立占配额,即使调用方复用同一个回调。
511
- const listener = onEvent ? (event) => onEvent(event) : undefined;
512
- if (listener && !isTerminal(e.view.state)) {
513
- if (e.listeners.size >= LIMITS.waiterLimit)
514
- throw Error(ERRORS.WAITER_LIMIT);
515
- e.listeners.add(listener);
516
- }
517
- const accepted = {
518
- protocolVersion: 1,
519
- messageId: randomUUID(),
520
- requestId: e.view.requestId,
521
- correlationId: id,
522
- sequence: e.view.sequence,
523
- type: "accepted",
524
- payload: { state: e.view.state, durable: false },
525
- };
526
- return {
527
- accepted,
528
- done: e.done,
529
- /** 本次附着是否复用已有逻辑执行(结果标记可见性用)。 */
530
- reused,
531
- /** 当前快照:供仅在停止等待时使用,不改变交付标记。 */
532
- snapshot: () => this.view(e),
533
- cancel: () => this.cancel(e),
534
- detach: () => {
535
- if (listener)
536
- e.listeners.delete(listener);
537
- },
538
- returned: () => {
539
- if (e.view.delivery !== "returned")
540
- this.telemetry.record(e.trace, "returned");
541
- e.view.delivery = "returned";
542
- },
543
- };
544
- }
545
- command(owner, raw) {
546
- const m = parse(raw);
547
- if (m.type === "request")
548
- throw Error(ERRORS.EXPECTED_CONTROL);
549
- if (Object.keys(m.payload).length)
550
- throw Error(ERRORS.INVALID_CONTROL);
551
- this.sweep();
552
- const e = this.get(owner, m.requestId);
553
- const previous = e.messages.get(m.messageId), fingerprint = hashMessage(m);
554
- if (previous && previous !== fingerprint)
555
- throw Error(ERRORS.MESSAGE_CONFLICT);
556
- if (!previous) {
557
- // 保留一个应急控制槽:读流量绝不能阻塞取消。
558
- if (e.messages.size >= LIMITS.messageAccountLimit) {
559
- if (m.type !== "cancel")
560
- throw Error(ERRORS.MESSAGE_LIMIT);
561
- if (e.view.cancelRequested || isTerminal(e.view.state))
562
- return this.view(e);
563
- }
564
- e.messages.set(m.messageId, fingerprint);
565
- if (m.type === "cancel")
566
- this.cancel(e);
567
- }
568
- return this.view(e);
569
- }
570
- removeQueued(e) {
571
- this.queue = this.queue.filter((entry) => entry !== e);
572
- if (!this.queue.some((entry) => entry.owner === e.owner))
573
- this.readyOwners = this.readyOwners.filter((owner) => owner !== e.owner);
574
- }
575
- /** Trusted host owner required. Never exposes other sessions or business payloads. */
576
- inspect(owner) {
577
- if (!owner || typeof owner.sessionId !== "string" || !owner.sessionId)
578
- throw Error(ERRORS.OWNING_SESSION_REQUIRED);
579
- this.sweep();
580
- const now = performance.now();
581
- const records = [...this.entries.values()].filter((e) => e.owner === owner.sessionId);
582
- // 活跃任务不得被更新的终态历史挤到列表之外。
583
- const active = records.filter((e) => !isTerminal(e.view.state));
584
- const history = records.filter((e) => isTerminal(e.view.state)).reverse();
585
- const selected = [...active, ...history].slice(0, LIMITS.inspectTaskLimit);
586
- const tasks = selected.map((e) => ({
587
- requestId: e.view.requestId,
588
- state: e.view.state,
589
- delivery: e.view.delivery,
590
- cancelRequested: e.view.cancelRequested,
591
- error: e.view.error ?? null,
592
- progress: e.view.progress ?? null,
593
- waitReason: e.view.state === "queued"
594
- ? this.running >= this.options.concurrency
595
- ? "concurrency"
596
- : (this.blockedBy(e) ?? "dispatch")
597
- : null,
598
- longRunning: e.view.state === "running" &&
599
- e.startedAt !== undefined &&
600
- (e.finishedAt ?? now) - e.startedAt >=
601
- this.scheduler.longRunningMs,
602
- phase: isTerminal(e.view.state)
603
- ? e.view.delivery === "returned"
604
- ? "returned"
605
- : "terminal"
606
- : e.view.cancelRequested
607
- ? "cancelling"
608
- : e.view.state,
609
- queueMs: Math.max(0, (e.startedAt ?? e.finishedAt ?? now) - e.trace.at),
610
- runMs: e.startedAt === undefined
611
- ? 0
612
- : Math.max(0, (e.finishedAt ?? now) - e.startedAt),
613
- remainingQueueMs: e.view.state === "queued" && e.deadline !== undefined
614
- ? Math.max(0, e.deadline - now)
615
- : null,
616
- }));
617
- return freezeJson({
618
- phase: this.shutdownPhase,
619
- policy: { ...this.admission },
620
- counts: {
621
- queued: records.filter((e) => e.view.state === "queued").length,
622
- running: records.filter((e) => e.view.state === "running").length,
623
- terminal: records.filter((e) => isTerminal(e.view.state)).length,
624
- },
625
- tasks,
626
- truncated: records.length > LIMITS.inspectTaskLimit,
627
- });
628
- }
629
- cancel(e) {
630
- if (isTerminal(e.view.state) || e.view.cancelRequested)
631
- return;
632
- e.view.cancelRequested = true;
633
- this.telemetry.record(e.trace, "cancel_requested");
634
- e.controller.abort();
635
- this.emit(e, "cancel_requested", {});
636
- if (e.view.state === "queued") {
637
- this.removeQueued(e);
638
- this.finish(e, "cancelled");
639
- }
640
- }
641
- finish(e, state, result, error, resultBytes = 0) {
642
- if (isTerminal(e.view.state))
643
- return;
644
- this.releaseResources(e);
645
- if (e.dedupeKey !== undefined &&
646
- this.dedupeIndex.get(e.dedupeKey) === e &&
647
- state !== "succeeded")
648
- this.dedupeIndex.delete(e.dedupeKey);
649
- if (e.queueTimer !== undefined) {
650
- clearTimeout(e.queueTimer);
651
- e.queueTimer = undefined;
652
- }
653
- e.finishedAt = performance.now();
654
- // 终态事件是下一个序号;先用它构建快照,避免把不可逆终态提交到视图之外。
655
- const terminalSequence = e.view.sequence + 1;
656
- let next = { ...e.view, state, sequence: terminalSequence };
657
- if (result !== undefined)
658
- next.result = result;
659
- if (error)
660
- next.error = error;
661
- let snapshot;
662
- try {
663
- snapshot = freezeJson(structuredClone(next));
664
- }
665
- catch {
666
- state = "unknown";
667
- resultBytes = 0;
668
- next = {
669
- requestId: e.view.requestId,
670
- state,
671
- sequence: terminalSequence,
672
- cancelRequested: e.view.cancelRequested,
673
- delivery: e.view.delivery,
674
- error: "RESULT_NOT_RETAINED",
675
- };
676
- snapshot = freezeJson({ ...next });
677
- }
678
- e.resultBytes = resultBytes;
679
- this.retainedResultBytes += resultBytes;
680
- e.created = Date.now();
681
- // emit() 会再自增序号;先整体回退一档,使终态事件恰好落在 terminalSequence,
682
- // 与上面交付给等待者的快照序号完全一致。
683
- e.view = { ...next, sequence: terminalSequence - 1 };
684
- this.telemetry.record(e.trace, "terminal");
685
- e.resolve(snapshot);
686
- this.emit(e, state, { state });
687
- // 重入的终态通知可能仍排在本批之后。
688
- if (!e.dispatching)
689
- e.listeners.clear();
690
- }
691
- /** 按会话轮转选择下一个可执行项:会话内按类别优先级 + 老化,跨会话公平。 */
692
- nextQueuedIndex(now) {
693
- const owners = this.readyOwners.length;
694
- for (let n = 0; n < owners; n++) {
695
- const owner = this.readyOwners.shift();
696
- let chosen = -1;
697
- let chosenScore = Infinity;
698
- for (let i = 0; i < this.queue.length; i++) {
699
- const e = this.queue[i];
700
- if (e.owner !== owner || !this.eligible(e))
701
- continue;
702
- const score = this.effectivePriority(e, now);
703
- if (score < chosenScore) {
704
- chosen = i;
705
- chosenScore = score;
706
- }
707
- }
708
- if (chosen < 0) {
709
- // 该会话暂时全部被舱壁/资源阻塞;保留轮次,等释放后重新争取。
710
- if (this.queue.some((entry) => entry.owner === owner))
711
- this.readyOwners.push(owner);
712
- continue;
713
- }
714
- if (this.queue.some((entry, i) => i !== chosen && entry.owner === owner))
715
- this.readyOwners.push(owner);
716
- return chosen;
717
- }
718
- return -1;
719
- }
720
- pump() {
721
- while (!this.closed) {
722
- const now = performance.now();
723
- this.expireQueued(now);
724
- if (this.running >= this.options.concurrency || this.queue.length === 0)
725
- return;
726
- const index = this.nextQueuedIndex(now);
727
- if (index < 0)
728
- return;
729
- const e = this.queue.splice(index, 1)[0];
730
- if (e.view.state !== "queued")
731
- continue;
732
- if (e.queueTimer !== undefined) {
733
- clearTimeout(e.queueTimer);
734
- e.queueTimer = undefined;
735
- }
736
- e.startedAt = performance.now();
737
- this.running++;
738
- this.runningByHandler.set(e.handler, (this.runningByHandler.get(e.handler) ?? 0) + 1);
739
- for (const resource of e.resources)
740
- this.resourceLocks.set(resource, e);
741
- e.view.state = "running";
742
- this.telemetry.record(e.trace, "started");
743
- this.emit(e, "started", {});
744
- void this.run(e).finally(() => {
745
- this.running--;
746
- const remaining = (this.runningByHandler.get(e.handler) ?? 1) - 1;
747
- if (remaining > 0)
748
- this.runningByHandler.set(e.handler, remaining);
749
- else
750
- this.runningByHandler.delete(e.handler);
751
- this.pump();
752
- });
753
- }
754
- }
755
- async run(e) {
756
- try {
757
- const result = await e.handler.run(e.input, {
758
- invoke: e.invoke,
759
- signal: e.controller.signal,
760
- requestId: e.view.requestId,
761
- progress: (seq, pct) => {
762
- if (e.view.state !== "running" ||
763
- !Number.isSafeInteger(seq) ||
764
- seq <= e.sourceSequence ||
765
- !Number.isFinite(pct) ||
766
- pct < 0 ||
767
- pct > 100)
768
- return;
769
- e.sourceSequence = seq;
770
- e.view.progress = pct;
771
- this.emit(e, "progress", { percent: pct });
772
- },
773
- });
774
- let json;
775
- try {
776
- assertJson(result);
777
- json = JSON.stringify(result);
778
- }
779
- catch {
780
- this.finish(e, "unknown", undefined, "RESULT_NOT_RETAINED");
781
- return;
782
- }
783
- if (json === undefined ||
784
- Buffer.byteLength(json) > this.options.resultBytes) {
785
- this.finish(e, "unknown", undefined, "RESULT_NOT_RETAINED");
786
- return;
787
- }
788
- this.finish(e, "succeeded", JSON.parse(json), undefined, Buffer.byteLength(json));
789
- }
790
- catch (err) {
791
- const cancelled = e.controller.signal.aborted &&
792
- err instanceof Error &&
793
- err.name === "AbortError";
794
- this.finish(e, cancelled
795
- ? "cancelled"
796
- : e.controller.signal.aborted
797
- ? "unknown"
798
- : "failed", undefined, cancelled
799
- ? "CANCELLED"
800
- : e.controller.signal.aborted
801
- ? "CANCELLATION_UNCONFIRMED"
802
- : "HANDLER_FAILED");
803
- }
804
- }
805
- close() {
806
- if (this.closing)
807
- return this.closing;
808
- this.closed = true;
809
- this.shutdownPhase = "cancelling";
810
- let resolve, reject;
811
- this.closing = new Promise((r, j) => {
812
- resolve = r;
813
- reject = j;
814
- });
815
- void (async () => {
816
- const entries = [...this.entries.values()];
817
- for (const e of entries)
818
- this.cancel(e);
819
- this.shutdownPhase = "draining";
820
- await Promise.all(entries.map((e) => e.done));
821
- this.handlers.clear();
822
- this.entries.clear();
823
- this.dedupeIndex.clear();
824
- this.resourceLocks.clear();
825
- this.runningByHandler.clear();
826
- this.retainedResultBytes = 0;
827
- this.queue = [];
828
- this.readyOwners = [];
829
- this.shutdownPhase = "closed";
830
- })().then(resolve, reject);
831
- return this.closing;
832
- }
833
- }