@abelxiaoxing/cadence 1.2.1 → 1.2.3

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.
@@ -7,12 +7,12 @@ import {
7
7
  } from "./route-policy.ts";
8
8
 
9
9
  export const ROUTE_ATTEMPT_BOUNDS = Object.freeze({
10
- connectMs: 5_000,
11
- firstResponseMs: 30_000,
12
- idleMs: 60_000,
13
- totalMs: 10 * 60_000,
10
+ connectMs: 10_000,
11
+ firstResponseMs: 90_000,
12
+ idleMs: 3 * 60_000,
13
+ totalMs: 20 * 60_000,
14
14
  cooldownMs: 30_000,
15
- attemptsPerRoute: 1,
15
+ attemptsPerRoute: 2,
16
16
  });
17
17
 
18
18
  export type RouteHealthState = "healthy" | "open" | "half-open";
@@ -61,6 +61,7 @@ export interface RunWorkerAttemptInput<T> {
61
61
  requirements?: RouteRequirements;
62
62
  signal?: AbortSignal;
63
63
  onActivity?: (event: BrokerActivityUpdate) => void;
64
+ classifyResult?: (value: T) => string | undefined;
64
65
  execute(context: RouteAttemptContext): Promise<T>;
65
66
  }
66
67
 
@@ -218,6 +219,16 @@ export class WorkerBroker {
218
219
  this.#setRouteHealth(routeId, { state: "healthy" });
219
220
  }
220
221
 
222
+ #releaseHalfOpenProbeForRetry(routeId: string, code: string): void {
223
+ const health = this.#routeHealth(routeId);
224
+ if (health.state !== "half-open" || !health.probeInFlight) return;
225
+ this.#setRouteHealth(routeId, {
226
+ state: "open",
227
+ retryAt: this.#now(),
228
+ lastCode: code,
229
+ });
230
+ }
231
+
221
232
  select(input: {
222
233
  role: WorkerRole;
223
234
  dialects?: RouteDialect[];
@@ -372,6 +383,7 @@ export class WorkerBroker {
372
383
  requirements?: RouteRequirements;
373
384
  signal?: AbortSignal;
374
385
  onActivity?: (event: BrokerActivityUpdate) => void;
386
+ classifyResult?: (value: T) => string | undefined;
375
387
  execute: (context: RouteAttemptContext) => Promise<T>;
376
388
  }): Promise<
377
389
  | { ok: true; routeId: string; value: T; attempts: BrokerAttemptEvidence[] }
@@ -397,12 +409,20 @@ export class WorkerBroker {
397
409
  }
398
410
  const attempted = new Set<string>();
399
411
  const evidence: BrokerAttemptEvidence[] = [];
400
- const maxAttempts = this.#policy.roles[input.role].filter((routeId) => {
401
- const route = this.#policy.routes[routeId];
402
- return route
403
- ? capable(route, input.role, input.requirements ?? {})
404
- : false;
405
- }).length;
412
+ const capableRouteCount = this.#policy.roles[input.role].filter(
413
+ (routeId) => {
414
+ const route = this.#policy.routes[routeId];
415
+ return route
416
+ ? capable(route, input.role, input.requirements ?? {})
417
+ : false;
418
+ },
419
+ ).length;
420
+ const maxAttempts = Math.max(
421
+ 1,
422
+ capableRouteCount > 1
423
+ ? capableRouteCount
424
+ : ROUTE_ATTEMPT_BOUNDS.attemptsPerRoute,
425
+ );
406
426
  for (;;) {
407
427
  const selection = this.select({
408
428
  role: input.role,
@@ -427,6 +447,9 @@ export class WorkerBroker {
427
447
  probeExpiresAt: this.#now() + ROUTE_ATTEMPT_BOUNDS.totalMs,
428
448
  });
429
449
  }
450
+ const routeAttempt = evidence.filter(
451
+ (item) => item.routeId === routeId,
452
+ ).length;
430
453
  const attempt = evidence.length + 1;
431
454
  emitBrokerActivity(input.onActivity, {
432
455
  state: "connecting",
@@ -450,6 +473,31 @@ export class WorkerBroker {
450
473
  : "worker-progress",
451
474
  }),
452
475
  );
476
+ const semanticCode = input.classifyResult?.(value);
477
+ if (semanticCode) {
478
+ evidence.push({ routeId, code: semanticCode });
479
+ const routeHasRetry =
480
+ capableRouteCount === 1 &&
481
+ routeAttempt + 1 < ROUTE_ATTEMPT_BOUNDS.attemptsPerRoute;
482
+ if (routeHasRetry) {
483
+ this.#releaseHalfOpenProbeForRetry(routeId, semanticCode);
484
+ attempted.delete(routeId);
485
+ } else {
486
+ this.markFailure(routeId, semanticCode);
487
+ }
488
+ if (attempt < maxAttempts) {
489
+ emitBrokerActivity(input.onActivity, {
490
+ state: "retrying",
491
+ attempt,
492
+ maxAttempts,
493
+ code: semanticCode,
494
+ wait: "bounded-policy",
495
+ });
496
+ continue;
497
+ }
498
+ if (routeHasRetry) this.markFailure(routeId, semanticCode);
499
+ return { ok: true, routeId, value, attempts: evidence };
500
+ }
453
501
  this.markSuccess(routeId);
454
502
  return { ok: true, routeId, value, attempts: evidence };
455
503
  } catch (error) {
@@ -466,8 +514,16 @@ export class WorkerBroker {
466
514
  }
467
515
  return { ok: false, state: "cancelled", code, attempts: evidence };
468
516
  }
469
- this.markFailure(routeId, code);
470
- if (attempted.size < maxAttempts) {
517
+ const routeHasRetry =
518
+ capableRouteCount === 1 &&
519
+ routeAttempt + 1 < ROUTE_ATTEMPT_BOUNDS.attemptsPerRoute;
520
+ if (routeHasRetry) {
521
+ this.#releaseHalfOpenProbeForRetry(routeId, code);
522
+ attempted.delete(routeId);
523
+ } else {
524
+ this.markFailure(routeId, code);
525
+ }
526
+ if (attempt < maxAttempts) {
471
527
  emitBrokerActivity(input.onActivity, {
472
528
  state: "retrying",
473
529
  attempt,
@@ -475,6 +531,8 @@ export class WorkerBroker {
475
531
  code,
476
532
  wait: "bounded-policy",
477
533
  });
534
+ } else if (routeHasRetry) {
535
+ this.markFailure(routeId, code);
478
536
  }
479
537
  }
480
538
  }
@@ -615,6 +673,7 @@ export class RunWorkerBroker {
615
673
  ...(input.requirements ? { requirements: input.requirements } : {}),
616
674
  ...(input.signal ? { signal: input.signal } : {}),
617
675
  ...(input.onActivity ? { onActivity: input.onActivity } : {}),
676
+ ...(input.classifyResult ? { classifyResult: input.classifyResult } : {}),
618
677
  execute: input.execute,
619
678
  });
620
679
  if (result.ok) {