deepline 0.3.322 → 0.3.324
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +0 -18
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +454 -129
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +407 -37
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +16 -1
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-http-request.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +15 -5
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +7 -2
- package/dist/bundling-sources/shared_libs/play-runtime/provider-row-retry-queue.ts +65 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +34 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +116 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +59 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +294 -37
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +73 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +41 -3
- package/dist/cli/{auth-VSXORUWX.mjs → auth-6KWNR6AI.mjs} +4 -4
- package/dist/cli/auth-LKSTC3YV.js +31 -0
- package/dist/cli/{billing-Q6WO5E2R.js → billing-CLLWHIAT.js} +70 -70
- package/dist/cli/{billing-EH74RDBF.mjs → billing-VQ2NUJLB.mjs} +4 -4
- package/dist/cli/{chunk-M4TZIO5U.js → chunk-2UQUOUAW.js} +2 -2
- package/dist/cli/{chunk-JWEYKLOV.mjs → chunk-3ZKAKQYC.mjs} +1 -1
- package/dist/cli/{chunk-6JV2VSQN.mjs → chunk-5CQ554BM.mjs} +1 -1
- package/dist/cli/{chunk-Z5TQC2ID.mjs → chunk-5QZFWY7C.mjs} +2 -2
- package/dist/cli/{chunk-A3THNBGB.mjs → chunk-AKM5JVPG.mjs} +2 -2
- package/dist/cli/{chunk-U6N6MS6N.js → chunk-D5YJHGST.js} +4 -4
- package/dist/cli/{chunk-SISYT2DM.mjs → chunk-DO2VA5UP.mjs} +2 -2
- package/dist/cli/{chunk-H52C7AHC.mjs → chunk-EHSI2DMO.mjs} +4 -4
- package/dist/cli/{chunk-CNO37AI3.js → chunk-EXKRHZRG.js} +220 -220
- package/dist/cli/{chunk-JCZX5ZQW.js → chunk-G4F2AIQ5.js} +6 -6
- package/dist/cli/{chunk-GAFGAIVW.js → chunk-GMO3NBE2.js} +60 -60
- package/dist/cli/{chunk-4F4XU2Z5.js → chunk-GROXVGGW.js} +6 -6
- package/dist/cli/{chunk-I7UUOAZN.mjs → chunk-HHZDWBSQ.mjs} +1 -1
- package/dist/cli/{chunk-ZHIPP7YD.mjs → chunk-I7RLL7LN.mjs} +1 -1
- package/dist/cli/{chunk-PTOQKD2Y.js → chunk-IXYR65PW.js} +1 -1
- package/dist/cli/{chunk-LUAF3MIC.mjs → chunk-LHJKYMTS.mjs} +1 -1
- package/dist/cli/{chunk-5RPFAX7O.js → chunk-N5E66DP3.js} +6 -6
- package/dist/cli/{chunk-CMRWPMZS.js → chunk-NC2ZXG2Y.js} +26 -26
- package/dist/cli/{chunk-LUTXK4ON.mjs → chunk-NIOFMRYW.mjs} +5 -5
- package/dist/cli/{chunk-PMTJYMDJ.mjs → chunk-NQJQ3CLX.mjs} +3 -3
- package/dist/cli/{chunk-KAUABQOG.js → chunk-PAA4FYCF.js} +15 -15
- package/dist/cli/{chunk-QXIPQYZZ.js → chunk-QM2Z4FQF.js} +6 -6
- package/dist/cli/{chunk-WARD6DBW.mjs → chunk-TJGJTB5T.mjs} +1 -1
- package/dist/cli/{chunk-KHRWZU6B.js → chunk-TZZNI2A4.js} +50 -50
- package/dist/cli/{chunk-6PE2VHSG.mjs → chunk-U2RPP53P.mjs} +6 -6
- package/dist/cli/{chunk-Y3LPZJFE.js → chunk-V5PA6TCT.js} +48 -48
- package/dist/cli/{client-XRO4X5XF.mjs → client-VHUKTDAZ.mjs} +3 -3
- package/dist/cli/client-WMSTRUGP.js +20 -0
- package/dist/cli/{csv-ALZ44QHM.js → csv-5MZY7HU5.js} +12 -12
- package/dist/cli/{csv-5B6JDXCN.mjs → csv-7DA4APB6.mjs} +3 -3
- package/dist/cli/{db-HJECAUNU.mjs → db-7UPW3N5U.mjs} +4 -4
- package/dist/cli/{db-D6UHGVOU.js → db-ICG3FUVE.js} +20 -20
- package/dist/cli/{describe-YAN46NBC.js → describe-VDRY2JCE.js} +15 -15
- package/dist/cli/{describe-FBAWTMDS.mjs → describe-ZAPGM5ND.mjs} +8 -8
- package/dist/cli/{executions-XSV7ZE4N.js → executions-D5GYCY3A.js} +6 -6
- package/dist/cli/{executions-LWHMAUHH.mjs → executions-TK3C2FAQ.mjs} +4 -4
- package/dist/cli/{feedback-RKT6F6W6.js → feedback-PZAZZUXF.js} +6 -6
- package/dist/cli/{feedback-EPXMI55U.mjs → feedback-T4EIP5QR.mjs} +3 -3
- package/dist/cli/git-73N6EUZ3.js +23 -0
- package/dist/cli/{git-BOXANJBI.mjs → git-WBFWIVO5.mjs} +4 -4
- package/dist/cli/index.js +75 -75
- package/dist/cli/index.mjs +31 -31
- package/dist/cli/{legacy-noops-VV7DRGA7.mjs → legacy-noops-DUCK7OKN.mjs} +4 -4
- package/dist/cli/{legacy-noops-7EBEYUQZ.js → legacy-noops-LRBJWXRK.js} +7 -7
- package/dist/cli/{monitors-RDRG3E4Y.js → monitors-7S2SL62G.js} +85 -85
- package/dist/cli/{monitors-NJPHRFYT.mjs → monitors-WYH5AQIO.mjs} +4 -4
- package/dist/cli/{org-ZFWDBQ7R.js → org-EQNGGHPV.js} +60 -60
- package/dist/cli/{org-USE3QRY6.mjs → org-XCSMLTUW.mjs} +4 -4
- package/dist/cli/{play-3VEIZZJ5.mjs → play-A5ISFBJM.mjs} +7 -7
- package/dist/cli/play-CLC2Z5SO.js +152 -0
- package/dist/cli/{providers-SXWPIPKO.js → providers-DR2CLD3N.js} +7 -7
- package/dist/cli/{providers-PP4L5HLM.mjs → providers-KHTZFQ5I.mjs} +4 -4
- package/dist/cli/{quickstart-QCSPS2D5.mjs → quickstart-KT4LKORA.mjs} +3 -3
- package/dist/cli/{quickstart-WVLEQQXO.js → quickstart-NIRWI6YU.js} +5 -5
- package/dist/cli/search-DM7IQS5F.js +26 -0
- package/dist/cli/{search-LTC6R6IS.mjs → search-WCEXPBAT.mjs} +5 -5
- package/dist/cli/{secrets-R5FGK6UC.js → secrets-QSFEZGVE.js} +13 -13
- package/dist/cli/{secrets-LV4FJNAL.mjs → secrets-TN7DW3QR.mjs} +4 -4
- package/dist/cli/{sessions-N6CMP6XZ.js → sessions-LJS6W67T.js} +5 -5
- package/dist/cli/{sessions-CFNRSRYX.mjs → sessions-WSE5FN4H.mjs} +4 -4
- package/dist/cli/{settings-RNEJQGBL.js → settings-FGS3VYJN.js} +74 -74
- package/dist/cli/{settings-ADEYLHV7.mjs → settings-Z3V53A6J.mjs} +7 -7
- package/dist/cli/{setup-YLEOL7E7.mjs → setup-GMH4VJS3.mjs} +6 -6
- package/dist/cli/{setup-NJCUXN4H.js → setup-ZA234HEY.js} +41 -41
- package/dist/cli/skills-6APAQV6W.js +28 -0
- package/dist/cli/{skills-WABXOGDA.mjs → skills-7G2RZMYH.mjs} +4 -4
- package/dist/cli/{tools-XQCGEX5J.mjs → tools-7EE4BGXO.mjs} +6 -6
- package/dist/cli/tools-EPYUFWPB.js +38 -0
- package/dist/cli/update-FPVX3XJC.js +28 -0
- package/dist/cli/{update-3W2NBTST.mjs → update-O5QEWS7K.mjs} +6 -6
- package/dist/cli/{workflow-GKFGFQ4D.mjs → workflow-KE54RUTR.mjs} +5 -5
- package/dist/cli/{workflow-NUVM7LQR.js → workflow-M5PAAW3O.js} +15 -15
- package/dist/index.js +6 -6
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +75 -74
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-MKFIE6KN.js +0 -31
- package/dist/cli/client-NITVYMZK.js +0 -20
- package/dist/cli/git-B3L74RFF.js +0 -23
- package/dist/cli/play-EONETVYE.js +0 -152
- package/dist/cli/search-XK2KJXQG.js +0 -26
- package/dist/cli/skills-VNAPDBFA.js +0 -28
- package/dist/cli/tools-XF5EQO4O.js +0 -38
- package/dist/cli/update-3TQMEJO5.js +0 -28
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type PacingRule,
|
|
27
27
|
type ProviderConcurrencyGrant,
|
|
28
28
|
type ProviderPacingGrant,
|
|
29
|
+
type ProviderPermitValidationOptions,
|
|
29
30
|
type RateStateBackend,
|
|
30
31
|
} from './rate-state-backend';
|
|
31
32
|
import {
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
InMemoryBudgetStateBackend,
|
|
38
39
|
type BudgetStateBackend,
|
|
39
40
|
} from './budget-state-backend';
|
|
41
|
+
import { PROVIDER_CONCURRENCY_SAFETY_LIMIT } from '../provider-capacity/provider-concurrency-policy';
|
|
40
42
|
|
|
41
43
|
export interface WorkLease {
|
|
42
44
|
/** Free the slot / pacing permit. Idempotent. MUST be called in a finally. */
|
|
@@ -68,7 +70,10 @@ export interface ProviderPermit extends WorkLease {
|
|
|
68
70
|
*/
|
|
69
71
|
park(): Promise<void>;
|
|
70
72
|
/** Recheck shared provider state immediately before network dispatch. */
|
|
71
|
-
validate?(
|
|
73
|
+
validate?(
|
|
74
|
+
signal?: AbortSignal,
|
|
75
|
+
options?: ProviderPermitValidationOptions,
|
|
76
|
+
): Promise<boolean>;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
export type BudgetKind = 'toolCall' | 'retry' | 'waterfallStep';
|
|
@@ -179,7 +184,11 @@ export interface PlayExecutionGovernor {
|
|
|
179
184
|
*/
|
|
180
185
|
acquireProviderPermit(
|
|
181
186
|
toolId: string,
|
|
182
|
-
opts?: {
|
|
187
|
+
opts?: {
|
|
188
|
+
signal?: AbortSignal;
|
|
189
|
+
yieldProviderHolds?: boolean;
|
|
190
|
+
retryPriority?: boolean;
|
|
191
|
+
},
|
|
183
192
|
): Promise<ProviderPermit>;
|
|
184
193
|
|
|
185
194
|
/**
|
|
@@ -444,9 +453,12 @@ export function createPlayExecutionGovernor(
|
|
|
444
453
|
gatewayDispatchProof: rateLease.gatewayDispatchProof ?? null,
|
|
445
454
|
...(rateLease.validate
|
|
446
455
|
? {
|
|
447
|
-
validate: async (
|
|
456
|
+
validate: async (
|
|
457
|
+
signal?: AbortSignal,
|
|
458
|
+
options?: ProviderPermitValidationOptions,
|
|
459
|
+
) => {
|
|
448
460
|
if (settled) return false;
|
|
449
|
-
return await rateLease.validate!();
|
|
461
|
+
return await rateLease.validate!(signal, options);
|
|
450
462
|
},
|
|
451
463
|
}
|
|
452
464
|
: {}),
|
|
@@ -519,6 +531,7 @@ export function createPlayExecutionGovernor(
|
|
|
519
531
|
ruleId: 'default:provider-scope',
|
|
520
532
|
requestsPerWindow: DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS,
|
|
521
533
|
windowMs: 1_000,
|
|
534
|
+
maxConcurrency: PROVIDER_CONCURRENCY_SAFETY_LIMIT,
|
|
522
535
|
}));
|
|
523
536
|
}
|
|
524
537
|
providerByTool.set(toolId, pacing.provider);
|
|
@@ -661,6 +674,7 @@ export function createPlayExecutionGovernor(
|
|
|
661
674
|
rules: pacing.rules,
|
|
662
675
|
signal: opts?.signal,
|
|
663
676
|
yieldProviderHolds: opts?.yieldProviderHolds,
|
|
677
|
+
...(opts?.retryPriority ? { retryPriority: true } : {}),
|
|
664
678
|
});
|
|
665
679
|
return providerPermitFrom(
|
|
666
680
|
pacing.provider,
|
|
@@ -17,6 +17,10 @@ export const DEFAULT_ADVISORY_PROVIDER_MAX_RPS = 100;
|
|
|
17
17
|
/** Cold-start rate for an unknown provider bucket shared across a fleet key. */
|
|
18
18
|
export const DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS = 10;
|
|
19
19
|
|
|
20
|
+
/** Recover quickly to useful seed capacity while leaving quota headroom. */
|
|
21
|
+
export const DEFAULT_UNKNOWN_PROVIDER_FAST_RECOVERY_RPS =
|
|
22
|
+
DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS * 0.9;
|
|
23
|
+
|
|
20
24
|
/** Learned-rate floor, independent of the unknown provider's starting guess. */
|
|
21
25
|
export const DEFAULT_UNKNOWN_PROVIDER_MIN_RPS = 2;
|
|
22
26
|
|
|
@@ -93,7 +97,10 @@ export interface PacingPermit {
|
|
|
93
97
|
*/
|
|
94
98
|
park?(): Promise<void>;
|
|
95
99
|
/** Recheck the durable provider-scope hold immediately before dispatch. */
|
|
96
|
-
validate?(
|
|
100
|
+
validate?(
|
|
101
|
+
signal?: AbortSignal,
|
|
102
|
+
options?: ProviderPermitValidationOptions,
|
|
103
|
+
): Promise<boolean>;
|
|
97
104
|
/** Server-authored context for attributing this request's provider 429. */
|
|
98
105
|
readonly concurrencyGrant?: ProviderConcurrencyGrant | null;
|
|
99
106
|
/** Effective shared rate for every app-runtime provider permit. */
|
|
@@ -103,9 +110,15 @@ export interface PacingPermit {
|
|
|
103
110
|
readonly bucketId: string;
|
|
104
111
|
readonly rateScopeToken: string;
|
|
105
112
|
readonly retryAtMsAtGrant: number;
|
|
113
|
+
readonly dispatchValidationSupported?: boolean;
|
|
106
114
|
} | null;
|
|
107
115
|
}
|
|
108
116
|
|
|
117
|
+
export type ProviderPermitValidationOptions = {
|
|
118
|
+
/** The request carries this permit's signed proof to the execution gateway. */
|
|
119
|
+
dispatchValidationAtGateway?: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
109
122
|
/**
|
|
110
123
|
* Discriminates the backend so the Governor can adapt its policy to it. Only
|
|
111
124
|
* `app_runtime_postgres` (the Absurd/Node substrate's DB-backed pacer) is
|
|
@@ -137,6 +150,8 @@ export interface RateStateBackend {
|
|
|
137
150
|
signal?: AbortSignal;
|
|
138
151
|
/** Managed Plays hand a shared provider hold to durable row suspension. */
|
|
139
152
|
yieldProviderHolds?: boolean;
|
|
153
|
+
/** Prefer an already-started logical call over fresh permit waiters. */
|
|
154
|
+
retryPriority?: boolean;
|
|
140
155
|
}): Promise<PacingPermit>;
|
|
141
156
|
|
|
142
157
|
/**
|
|
@@ -503,9 +503,13 @@ export class MapExecutionProgress {
|
|
|
503
503
|
});
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
|
|
506
|
+
get activeResolvers(): number {
|
|
507
|
+
return this.active;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
resolverStarted(input: { retry?: boolean } = {}): void {
|
|
507
511
|
this.tracksActivity = true;
|
|
508
|
-
this.started += 1;
|
|
512
|
+
if (!input.retry) this.started += 1;
|
|
509
513
|
this.active += 1;
|
|
510
514
|
}
|
|
511
515
|
|
|
@@ -291,7 +291,7 @@ export async function sendPlayHttpRequest(input: {
|
|
|
291
291
|
try {
|
|
292
292
|
signal.throwIfAborted();
|
|
293
293
|
const valid =
|
|
294
|
-
!acquired.validate || (await acquired.validate());
|
|
294
|
+
!acquired.validate || (await acquired.validate(signal));
|
|
295
295
|
signal.throwIfAborted();
|
|
296
296
|
if (valid) return acquired;
|
|
297
297
|
} catch (error) {
|
|
@@ -88,6 +88,8 @@ export type PlayRunnerRateStateAcquireResult = {
|
|
|
88
88
|
waitMs: number;
|
|
89
89
|
/** This receiver serves rate_state_validate. Absent on pre-expansion readers. */
|
|
90
90
|
scopeValidationSupported?: boolean;
|
|
91
|
+
/** This gateway rechecks signed provider permits on integration execute. */
|
|
92
|
+
dispatchValidationSupported?: boolean;
|
|
91
93
|
/**
|
|
92
94
|
* Gateway clock used to produce `scheduledAtMs`. Runners translate the
|
|
93
95
|
* schedule into relative delays so cross-machine clock skew cannot compress
|
|
@@ -7,6 +7,8 @@ export type Gradient2ConcurrencyControllerOptions = Readonly<{
|
|
|
7
7
|
rttTolerance: number;
|
|
8
8
|
/** Bounded cold-start probe; one or below preserves ordinary Gradient2. */
|
|
9
9
|
startupProbeMultiplier?: number;
|
|
10
|
+
/** Reprobe undeclared capacity after an entire healthy completion window. */
|
|
11
|
+
recoveryProbeMultiplier?: number;
|
|
10
12
|
}>;
|
|
11
13
|
|
|
12
14
|
export type Gradient2Observation = Readonly<{
|
|
@@ -63,6 +65,7 @@ export class Gradient2ConcurrencyController {
|
|
|
63
65
|
readonly #longWindowSmoothing: number;
|
|
64
66
|
readonly #rttTolerance: number;
|
|
65
67
|
readonly #startupProbeMultiplier: number;
|
|
68
|
+
readonly #recoveryProbeMultiplier: number;
|
|
66
69
|
#limit: number;
|
|
67
70
|
#shortRttMs: number | null = null;
|
|
68
71
|
#longRttMs: number | null = null;
|
|
@@ -95,6 +98,13 @@ export class Gradient2ConcurrencyController {
|
|
|
95
98
|
1,
|
|
96
99
|
positive(options.startupProbeMultiplier ?? 1, 'Startup probe multiplier'),
|
|
97
100
|
);
|
|
101
|
+
this.#recoveryProbeMultiplier = Math.max(
|
|
102
|
+
1,
|
|
103
|
+
positive(
|
|
104
|
+
options.recoveryProbeMultiplier ?? 1,
|
|
105
|
+
'Recovery probe multiplier',
|
|
106
|
+
),
|
|
107
|
+
);
|
|
98
108
|
this.#windowStartedAtLimit = this.#limit;
|
|
99
109
|
if (checkpoint) {
|
|
100
110
|
this.#limit = clamp(
|
|
@@ -171,13 +181,13 @@ export class Gradient2ConcurrencyController {
|
|
|
171
181
|
0.5,
|
|
172
182
|
1,
|
|
173
183
|
);
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
this.#startupProbeMultiplier
|
|
177
|
-
|
|
184
|
+
const probeMultiplier = this.#hasObservedThrottle
|
|
185
|
+
? this.#recoveryProbeMultiplier
|
|
186
|
+
: this.#startupProbeMultiplier;
|
|
187
|
+
const probeHealthy = probeMultiplier > 1 && gradient >= 1 - this.#smoothing;
|
|
178
188
|
if (probeHealthy) {
|
|
179
189
|
this.#limit = clamp(
|
|
180
|
-
this.#limit *
|
|
190
|
+
this.#limit * probeMultiplier,
|
|
181
191
|
this.#minimumLimit,
|
|
182
192
|
this.#maximumLimit,
|
|
183
193
|
);
|
|
@@ -15,16 +15,21 @@ export const PROVIDER_CONCURRENCY_CONTROLLER_TUNING = Object.freeze({
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The one Gradient2 policy used by Postgres enforcement, shadow evaluation,
|
|
18
|
-
* and simulations. Callers choose
|
|
19
|
-
*
|
|
18
|
+
* and simulations. Callers choose the server-authored limits and whether the
|
|
19
|
+
* scope is undeclared; algorithm tuning must not drift by deployment surface.
|
|
20
20
|
*/
|
|
21
21
|
export function providerConcurrencyControllerOptions(input: {
|
|
22
22
|
initialLimit: number;
|
|
23
23
|
maximumLimit?: number;
|
|
24
|
+
unknownProvider?: boolean;
|
|
24
25
|
}): Gradient2ConcurrencyControllerOptions {
|
|
25
26
|
return Object.freeze({
|
|
26
27
|
...PROVIDER_CONCURRENCY_CONTROLLER_TUNING,
|
|
27
28
|
initialLimit: input.initialLimit,
|
|
28
29
|
maximumLimit: input.maximumLimit ?? PROVIDER_CONCURRENCY_SAFETY_LIMIT,
|
|
30
|
+
recoveryProbeMultiplier: input.unknownProvider
|
|
31
|
+
? PROVIDER_CAPACITY_POLICY.concurrency
|
|
32
|
+
.unknownProviderRecoveryProbeMultiplier
|
|
33
|
+
: 1,
|
|
29
34
|
});
|
|
30
35
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type ProviderRowRetry = {
|
|
2
|
+
rowIndex: number;
|
|
3
|
+
provider: string;
|
|
4
|
+
retryAtMs: number;
|
|
5
|
+
readyAtMs: number;
|
|
6
|
+
/** Ready work may run, but this row must not wait in an otherwise idle map. */
|
|
7
|
+
residentWaitBudgetExhausted?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Deadlines belong to rows; a long hold must not hide another ready row. */
|
|
11
|
+
export class ProviderRowRetryQueue {
|
|
12
|
+
private readonly rows: ProviderRowRetry[] = [];
|
|
13
|
+
|
|
14
|
+
get size(): number {
|
|
15
|
+
return this.rows.length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
peek(): ProviderRowRetry | undefined {
|
|
19
|
+
return this.rows[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
add(row: ProviderRowRetry): void {
|
|
23
|
+
let index = this.rows.length;
|
|
24
|
+
this.rows.push(row);
|
|
25
|
+
while (index > 0) {
|
|
26
|
+
const parentIndex = Math.floor((index - 1) / 2);
|
|
27
|
+
const parent = this.rows[parentIndex]!;
|
|
28
|
+
if (!this.precedes(row, parent)) break;
|
|
29
|
+
this.rows[index] = parent;
|
|
30
|
+
index = parentIndex;
|
|
31
|
+
}
|
|
32
|
+
this.rows[index] = row;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
takeReady(nowMs: number): ProviderRowRetry | undefined {
|
|
36
|
+
const first = this.peek();
|
|
37
|
+
if (!first || first.readyAtMs > nowMs) return undefined;
|
|
38
|
+
const last = this.rows.pop()!;
|
|
39
|
+
if (this.rows.length === 0) return first;
|
|
40
|
+
let index = 0;
|
|
41
|
+
while (true) {
|
|
42
|
+
const left = index * 2 + 1;
|
|
43
|
+
if (left >= this.rows.length) break;
|
|
44
|
+
const right = left + 1;
|
|
45
|
+
const childIndex =
|
|
46
|
+
right < this.rows.length &&
|
|
47
|
+
this.precedes(this.rows[right]!, this.rows[left]!)
|
|
48
|
+
? right
|
|
49
|
+
: left;
|
|
50
|
+
const child = this.rows[childIndex]!;
|
|
51
|
+
if (!this.precedes(child, last)) break;
|
|
52
|
+
this.rows[index] = child;
|
|
53
|
+
index = childIndex;
|
|
54
|
+
}
|
|
55
|
+
this.rows[index] = last;
|
|
56
|
+
return first;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private precedes(left: ProviderRowRetry, right: ProviderRowRetry): boolean {
|
|
60
|
+
return (
|
|
61
|
+
left.readyAtMs < right.readyAtMs ||
|
|
62
|
+
(left.readyAtMs === right.readyAtMs && left.rowIndex < right.rowIndex)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -770,6 +770,7 @@ export interface RuntimeResourceGovernor {
|
|
|
770
770
|
toolId: string;
|
|
771
771
|
signal?: AbortSignal;
|
|
772
772
|
yieldProviderHolds?: boolean;
|
|
773
|
+
retryPriority?: boolean;
|
|
773
774
|
}): Promise<RuntimeProviderPermit>;
|
|
774
775
|
/**
|
|
775
776
|
* Non-blocking provider admission from capacity this process already holds.
|
|
@@ -1746,6 +1747,7 @@ export function createRuntimeResourceGovernor(input: {
|
|
|
1746
1747
|
{
|
|
1747
1748
|
signal: providerInput.signal,
|
|
1748
1749
|
yieldProviderHolds: providerInput.yieldProviderHolds,
|
|
1750
|
+
...(providerInput.retryPriority ? { retryPriority: true } : {}),
|
|
1749
1751
|
},
|
|
1750
1752
|
);
|
|
1751
1753
|
observe({
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* inventing a second runtime scheduler.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { ProviderPermitValidationOptions } from './governor/rate-state-backend';
|
|
12
|
+
|
|
11
13
|
export type RuntimeFlowLease = {
|
|
12
14
|
release(): void;
|
|
13
15
|
};
|
|
@@ -86,7 +88,10 @@ export type RuntimeDispatchProviderPermit = RuntimeFlowLease & {
|
|
|
86
88
|
cancel(): Promise<void>;
|
|
87
89
|
park?(): Promise<void>;
|
|
88
90
|
/** Recheck a shared provider hold at the physical dispatch boundary. */
|
|
89
|
-
validate?(
|
|
91
|
+
validate?(
|
|
92
|
+
signal?: AbortSignal,
|
|
93
|
+
options?: ProviderPermitValidationOptions,
|
|
94
|
+
): Promise<boolean>;
|
|
90
95
|
};
|
|
91
96
|
|
|
92
97
|
export type RuntimeDispatchFlowInput = {
|
|
@@ -99,6 +104,8 @@ export type RuntimeDispatchFlowInput = {
|
|
|
99
104
|
* outbound send. The callback must call the supplied boundary exactly once.
|
|
100
105
|
*/
|
|
101
106
|
deferProviderPermitValidation?: boolean;
|
|
107
|
+
/** The request forwards its signed permit to the execution gateway. */
|
|
108
|
+
providerDispatchAtGateway?: boolean;
|
|
102
109
|
/** Emits the request-side transport wait observed by the caller's metrics. */
|
|
103
110
|
onTransportAdmitted?: (waitMs: number) => void;
|
|
104
111
|
};
|
|
@@ -133,7 +140,15 @@ export type RuntimeDispatchScope = {
|
|
|
133
140
|
onPermitInvalidated?: () => void,
|
|
134
141
|
) => boolean | Promise<boolean>,
|
|
135
142
|
) => Output | Promise<Output>,
|
|
136
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Persist response feedback while the lease is live. Return
|
|
145
|
+
* `not_dispatched` only for a definite rejection before provider work;
|
|
146
|
+
* this refunds the reservation. Ambiguous outcomes retain release semantics.
|
|
147
|
+
*/
|
|
148
|
+
beforeRelease?: () =>
|
|
149
|
+
| void
|
|
150
|
+
| 'not_dispatched'
|
|
151
|
+
| Promise<void | 'not_dispatched'>,
|
|
137
152
|
): Promise<Output>;
|
|
138
153
|
};
|
|
139
154
|
|
|
@@ -306,7 +321,10 @@ export function createRuntimeDispatchFlow<
|
|
|
306
321
|
onPermitInvalidated?: () => void,
|
|
307
322
|
) => boolean | Promise<boolean>,
|
|
308
323
|
) => RequestOutput | Promise<RequestOutput>,
|
|
309
|
-
beforeRelease?: () =>
|
|
324
|
+
beforeRelease?: () =>
|
|
325
|
+
| void
|
|
326
|
+
| 'not_dispatched'
|
|
327
|
+
| Promise<void | 'not_dispatched'>,
|
|
310
328
|
): Promise<RequestOutput> => {
|
|
311
329
|
if (!scopeOpen) {
|
|
312
330
|
return Promise.reject(
|
|
@@ -348,7 +366,11 @@ export function createRuntimeDispatchFlow<
|
|
|
348
366
|
let replaced = false;
|
|
349
367
|
while (
|
|
350
368
|
permit.providerPermit.validate &&
|
|
351
|
-
!(await
|
|
369
|
+
!(await (admission.providerDispatchAtGateway
|
|
370
|
+
? permit.providerPermit.validate(admission.signal, {
|
|
371
|
+
dispatchValidationAtGateway: true,
|
|
372
|
+
})
|
|
373
|
+
: permit.providerPermit.validate(admission.signal)))
|
|
352
374
|
) {
|
|
353
375
|
requestStarted = false;
|
|
354
376
|
onPermitInvalidated?.();
|
|
@@ -412,7 +434,14 @@ export function createRuntimeDispatchFlow<
|
|
|
412
434
|
// always release all leases even when feedback persistence
|
|
413
435
|
// fails.
|
|
414
436
|
try {
|
|
415
|
-
if (
|
|
437
|
+
if (
|
|
438
|
+
requestStarted &&
|
|
439
|
+
(await beforeRelease?.()) === 'not_dispatched'
|
|
440
|
+
) {
|
|
441
|
+
// A definite receiver rejection returns the reservation.
|
|
442
|
+
// Ambiguous exchanges retain ordinary release semantics.
|
|
443
|
+
requestStarted = false;
|
|
444
|
+
}
|
|
416
445
|
} finally {
|
|
417
446
|
await releasePermit();
|
|
418
447
|
}
|
|
@@ -15,6 +15,7 @@ export type RuntimeMapRowFeedSnapshot = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const RUNTIME_MAP_ROW_FEED_MAX_WAIT_MS = 30 * 60 * 1000;
|
|
18
|
+
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
18
19
|
|
|
19
20
|
export class RuntimeMapRowFeedStalledError extends Error {
|
|
20
21
|
readonly snapshot: RuntimeMapRowFeedSnapshot;
|
|
@@ -51,7 +52,11 @@ export class RuntimeMapRowFeed {
|
|
|
51
52
|
#closed = false;
|
|
52
53
|
#failure: unknown = null;
|
|
53
54
|
#failed = false;
|
|
54
|
-
|
|
55
|
+
// A null index waits for pool activity/deadlines independently of source
|
|
56
|
+
// closure; an indexed waiter still means "more source rows".
|
|
57
|
+
readonly #rowWaiters = new Map<Waiter<boolean>, number | null>();
|
|
58
|
+
#workerWakeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
59
|
+
#workerWakeAtMs: number | null = null;
|
|
55
60
|
#drainWaiter: DrainWaiter | null = null;
|
|
56
61
|
#onAppended: ((fromIndex: number, toIndex: number) => void) | null = null;
|
|
57
62
|
readonly #maxWaitMs: number;
|
|
@@ -89,6 +94,7 @@ export class RuntimeMapRowFeed {
|
|
|
89
94
|
close(): void {
|
|
90
95
|
if (this.#closed || this.#failed) return;
|
|
91
96
|
this.#closed = true;
|
|
97
|
+
if (!this.#hasWorkerWakeWaiters()) this.#clearWorkerWake();
|
|
92
98
|
this.#wakeRowWaiters();
|
|
93
99
|
this.#settleDrainWaiter();
|
|
94
100
|
}
|
|
@@ -97,6 +103,7 @@ export class RuntimeMapRowFeed {
|
|
|
97
103
|
if (this.#failed) return;
|
|
98
104
|
this.#failed = true;
|
|
99
105
|
this.#failure = error;
|
|
106
|
+
this.#clearWorkerWake();
|
|
100
107
|
for (const [waiter] of this.#rowWaiters) {
|
|
101
108
|
clearTimeout(waiter.timer);
|
|
102
109
|
waiter.reject(error);
|
|
@@ -117,6 +124,38 @@ export class RuntimeMapRowFeed {
|
|
|
117
124
|
return this.#closed;
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
/** Recheck a provider retry deadline while the source prepares its next page. */
|
|
128
|
+
wakeWorkersAt(wakeAtMs: number): void {
|
|
129
|
+
if (!Number.isFinite(wakeAtMs)) {
|
|
130
|
+
throw new Error('Runtime map worker wake deadline must be finite.');
|
|
131
|
+
}
|
|
132
|
+
if (this.#failed || (this.#closed && !this.#hasWorkerWakeWaiters())) return;
|
|
133
|
+
if (this.#workerWakeAtMs !== null && this.#workerWakeAtMs <= wakeAtMs)
|
|
134
|
+
return;
|
|
135
|
+
this.#clearWorkerWake();
|
|
136
|
+
this.#workerWakeAtMs = wakeAtMs;
|
|
137
|
+
this.#scheduleWorkerWake();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#scheduleWorkerWake(): void {
|
|
141
|
+
const wakeAtMs = this.#workerWakeAtMs;
|
|
142
|
+
if (wakeAtMs === null) return;
|
|
143
|
+
const waitMs = Math.min(
|
|
144
|
+
MAX_TIMER_DELAY_MS,
|
|
145
|
+
Math.max(0, wakeAtMs - Date.now()),
|
|
146
|
+
);
|
|
147
|
+
this.#workerWakeTimer = setTimeout(() => {
|
|
148
|
+
this.#workerWakeTimer = null;
|
|
149
|
+
if (Date.now() < wakeAtMs) {
|
|
150
|
+
this.#scheduleWorkerWake();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.#workerWakeAtMs = null;
|
|
154
|
+
this.wakeWorkers();
|
|
155
|
+
}, waitMs);
|
|
156
|
+
this.#workerWakeTimer.unref?.();
|
|
157
|
+
}
|
|
158
|
+
|
|
120
159
|
markDispatched(count = 1): void {
|
|
121
160
|
if (!Number.isInteger(count) || count < 0) {
|
|
122
161
|
throw new Error(
|
|
@@ -144,28 +183,77 @@ export class RuntimeMapRowFeed {
|
|
|
144
183
|
}
|
|
145
184
|
|
|
146
185
|
/**
|
|
147
|
-
* Resolves true once row `index` has been appended
|
|
148
|
-
*
|
|
186
|
+
* Resolves true once row `index` has been appended or an explicitly scheduled
|
|
187
|
+
* worker wake requests a recheck, false once the feed is closed with no such
|
|
188
|
+
* row. Rejects with the feed's failure.
|
|
149
189
|
*/
|
|
150
190
|
waitForRows(index: number): Promise<boolean> {
|
|
151
191
|
if (this.#failed) return Promise.reject(this.#failure);
|
|
152
192
|
if (index < this.#appended) return Promise.resolve(true);
|
|
153
193
|
if (this.#closed) return Promise.resolve(false);
|
|
194
|
+
return this.#waitForRowsOrWake(index);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A retry can become ready while siblings work after the source closes. */
|
|
198
|
+
waitForWorkerWake(wakeAtMs: number): Promise<boolean> {
|
|
199
|
+
if (!Number.isFinite(wakeAtMs)) {
|
|
200
|
+
return Promise.reject(
|
|
201
|
+
new Error('Runtime map worker wake deadline must be finite.'),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (this.#failed) return Promise.reject(this.#failure);
|
|
205
|
+
if (wakeAtMs <= Date.now()) return Promise.resolve(true);
|
|
206
|
+
// A known provider hold is bounded by its own deadline, rather than the
|
|
207
|
+
// source's idle diagnostic. Leave indexed source waits on their usual bound.
|
|
208
|
+
const waiting = this.#waitForRowsOrWake(
|
|
209
|
+
null,
|
|
210
|
+
Math.max(this.#maxWaitMs, wakeAtMs - Date.now() + 1),
|
|
211
|
+
);
|
|
212
|
+
this.wakeWorkersAt(wakeAtMs);
|
|
213
|
+
return waiting;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Recheck retry ownership when the last active resolver returns its lease. */
|
|
217
|
+
wakeWorkers(): void {
|
|
218
|
+
this.#clearWorkerWake();
|
|
219
|
+
for (const [waiter] of this.#rowWaiters) {
|
|
220
|
+
clearTimeout(waiter.timer);
|
|
221
|
+
waiter.resolve(true);
|
|
222
|
+
}
|
|
223
|
+
this.#rowWaiters.clear();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#waitForRowsOrWake(
|
|
227
|
+
index: number | null,
|
|
228
|
+
maxWaitMs = this.#maxWaitMs,
|
|
229
|
+
): Promise<boolean> {
|
|
154
230
|
return new Promise<boolean>((resolve, reject) => {
|
|
155
231
|
const startedAt = Date.now();
|
|
232
|
+
const expiresAtMs = startedAt + maxWaitMs;
|
|
233
|
+
const onTimeout = () => {
|
|
234
|
+
const remainingMs = expiresAtMs - Date.now();
|
|
235
|
+
if (remainingMs > 0) {
|
|
236
|
+
waiter.timer = setTimeout(
|
|
237
|
+
onTimeout,
|
|
238
|
+
Math.min(MAX_TIMER_DELAY_MS, remainingMs),
|
|
239
|
+
);
|
|
240
|
+
waiter.timer.unref?.();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this.#rowWaiters.delete(waiter);
|
|
244
|
+
if (this.#rowWaiters.size === 0) this.#clearWorkerWake();
|
|
245
|
+
reject(
|
|
246
|
+
new RuntimeMapRowFeedStalledError({
|
|
247
|
+
waiter: 'worker',
|
|
248
|
+
waitMs: Date.now() - startedAt,
|
|
249
|
+
snapshot: this.snapshot(),
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
};
|
|
156
253
|
const waiter: Waiter<boolean> = {
|
|
157
254
|
resolve,
|
|
158
255
|
reject,
|
|
159
|
-
timer: setTimeout(()
|
|
160
|
-
this.#rowWaiters.delete(waiter);
|
|
161
|
-
reject(
|
|
162
|
-
new RuntimeMapRowFeedStalledError({
|
|
163
|
-
waiter: 'worker',
|
|
164
|
-
waitMs: Date.now() - startedAt,
|
|
165
|
-
snapshot: this.snapshot(),
|
|
166
|
-
}),
|
|
167
|
-
);
|
|
168
|
-
}, this.#maxWaitMs),
|
|
256
|
+
timer: setTimeout(onTimeout, Math.min(MAX_TIMER_DELAY_MS, maxWaitMs)),
|
|
169
257
|
};
|
|
170
258
|
waiter.timer.unref?.();
|
|
171
259
|
this.#rowWaiters.set(waiter, index);
|
|
@@ -216,13 +304,26 @@ export class RuntimeMapRowFeed {
|
|
|
216
304
|
};
|
|
217
305
|
}
|
|
218
306
|
|
|
307
|
+
#clearWorkerWake(): void {
|
|
308
|
+
if (this.#workerWakeTimer) clearTimeout(this.#workerWakeTimer);
|
|
309
|
+
this.#workerWakeTimer = null;
|
|
310
|
+
this.#workerWakeAtMs = null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#hasWorkerWakeWaiters(): boolean {
|
|
314
|
+
for (const index of this.#rowWaiters.values()) {
|
|
315
|
+
if (index === null) return true;
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
219
320
|
#wakeRowWaiters(): void {
|
|
220
321
|
for (const [waiter, index] of this.#rowWaiters) {
|
|
221
|
-
if (index < this.#appended) {
|
|
322
|
+
if (index !== null && index < this.#appended) {
|
|
222
323
|
this.#rowWaiters.delete(waiter);
|
|
223
324
|
clearTimeout(waiter.timer);
|
|
224
325
|
waiter.resolve(true);
|
|
225
|
-
} else if (this.#closed) {
|
|
326
|
+
} else if (this.#closed && index !== null) {
|
|
226
327
|
this.#rowWaiters.delete(waiter);
|
|
227
328
|
clearTimeout(waiter.timer);
|
|
228
329
|
waiter.resolve(false);
|