deepline 0.3.443 → 0.3.445
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/play-runtime/app-runtime-api.ts +27 -6
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +153 -31
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +32 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +9 -2
- package/dist/cli/{auth-CP2CAI7J.mjs → auth-QNWYRLML.mjs} +4 -4
- package/dist/cli/auth-ZA4FRBSX.js +40 -0
- package/dist/cli/{billing-JYSP3C76.js → billing-3HFPC32I.js} +73 -73
- package/dist/cli/{billing-N36FL3GI.mjs → billing-ULHQPQ2L.mjs} +4 -4
- package/dist/cli/{chunk-Y45RAHEA.mjs → chunk-2L2D2SXH.mjs} +2 -2
- package/dist/cli/{chunk-6NUGMJQA.mjs → chunk-3OBHZADJ.mjs} +1 -1
- package/dist/cli/{chunk-WOUFCLTM.mjs → chunk-3SELRVWB.mjs} +5 -5
- package/dist/cli/{chunk-EWXXWSQM.js → chunk-5JJ6JYTL.js} +6 -6
- package/dist/cli/{chunk-XIJL6UMV.js → chunk-6DQTJ3DY.js} +35 -35
- package/dist/cli/{chunk-DOKTNYMQ.mjs → chunk-6LVOC2HD.mjs} +1 -1
- package/dist/cli/{chunk-CGZ2KKPM.js → chunk-732QWWPG.js} +10 -10
- package/dist/cli/{chunk-MRW6XPDH.mjs → chunk-BEI6ZL6E.mjs} +1 -1
- package/dist/cli/{chunk-MZLZNISS.mjs → chunk-C54S4QPU.mjs} +2 -2
- package/dist/cli/{chunk-OSYT3QQD.js → chunk-COCZROBN.js} +26 -26
- package/dist/cli/{chunk-KZ2VAXB6.js → chunk-CQ3ZZ3RL.js} +16 -16
- package/dist/cli/{chunk-DPEPHLAF.mjs → chunk-E5JZH6CI.mjs} +1 -1
- package/dist/cli/{chunk-MWUTMJCU.js → chunk-EI2NN6HP.js} +1 -1
- package/dist/cli/{chunk-GVPHPLAX.mjs → chunk-FB7VBDA3.mjs} +3 -3
- package/dist/cli/{chunk-ZXJQ26QG.js → chunk-FC7LS3Y5.js} +50 -50
- package/dist/cli/{chunk-KJH4C2IJ.js → chunk-GGMJ2VTZ.js} +81 -81
- package/dist/cli/{chunk-YV6N4BF2.js → chunk-H6HZMADG.js} +52 -52
- package/dist/cli/{chunk-75VUSWF3.js → chunk-HFXG7GGH.js} +6 -6
- package/dist/cli/{chunk-FU7ELMKS.mjs → chunk-IXJN2NLA.mjs} +1 -1
- package/dist/cli/{chunk-HHOJXBQQ.mjs → chunk-LBZUIZKQ.mjs} +2 -2
- package/dist/cli/{chunk-LVM7CSAH.js → chunk-MMMA4OLB.js} +8 -8
- package/dist/cli/{chunk-L3CW2G7L.mjs → chunk-NHWNFLFK.mjs} +2 -2
- package/dist/cli/{chunk-757V3FDW.js → chunk-OAVHRVPA.js} +2 -2
- package/dist/cli/{chunk-MQYBXB2J.mjs → chunk-SAN2HUYZ.mjs} +8 -8
- package/dist/cli/{chunk-B6AWQQK6.mjs → chunk-SSRKG7I5.mjs} +3 -3
- package/dist/cli/{chunk-VXGLDGOF.mjs → chunk-T7SLOS2J.mjs} +4 -4
- package/dist/cli/{chunk-PXXIBSBG.js → chunk-TABMEHIP.js} +4 -4
- package/dist/cli/{chunk-K4ELTQAT.js → chunk-THZL4XHV.js} +8 -8
- package/dist/cli/{chunk-6RKZVPFA.js → chunk-TX6F2BB6.js} +2 -2
- package/dist/cli/{chunk-BE2GX674.js → chunk-V247SCRQ.js} +20 -20
- package/dist/cli/{chunk-FRZLWUM6.mjs → chunk-YXY6FCRU.mjs} +3 -3
- package/dist/cli/{chunk-4A45A2MH.mjs → chunk-ZRWX23RQ.mjs} +1 -1
- package/dist/cli/{csv-ZQFVZ4R3.js → csv-NAATVV3O.js} +12 -12
- package/dist/cli/{csv-Z53PJEPZ.mjs → csv-QXMJNWPE.mjs} +3 -3
- package/dist/cli/{db-4CNIBR5P.mjs → db-2ADNWVDN.mjs} +4 -4
- package/dist/cli/{db-OHBRQL2N.js → db-IB426AKH.js} +25 -25
- package/dist/cli/{describe-AEM7SYXF.js → describe-6T2HEQ2V.js} +16 -16
- package/dist/cli/{describe-GAR4MEGX.mjs → describe-UM4PAI2I.mjs} +12 -12
- package/dist/cli/{executions-CST4IDL3.js → executions-3WVCUBHH.js} +6 -6
- package/dist/cli/{executions-YBZPFAD6.mjs → executions-4NCMXHDI.mjs} +4 -4
- package/dist/cli/{feedback-MEJZGB4C.js → feedback-C4IBRZKH.js} +7 -7
- package/dist/cli/{feedback-FHJE6YDU.mjs → feedback-OKLN3VLD.mjs} +3 -3
- package/dist/cli/{git-FIGXIUM5.mjs → git-A6XP6LIW.mjs} +4 -4
- package/dist/cli/git-RRZOWDCN.js +24 -0
- package/dist/cli/index.js +131 -131
- package/dist/cli/index.mjs +36 -36
- package/dist/cli/{legacy-noops-BE3VVOYG.js → legacy-noops-ALY2PBIT.js} +7 -7
- package/dist/cli/{legacy-noops-AXHAIAMH.mjs → legacy-noops-LCQXCYQY.mjs} +4 -4
- package/dist/cli/{monitors-5OTJUY43.js → monitors-5C6PSVAT.js} +84 -84
- package/dist/cli/{monitors-YTWELXP7.mjs → monitors-T3YN2RYD.mjs} +4 -4
- package/dist/cli/{org-Y5DMTECB.js → org-4V76A3LD.js} +66 -66
- package/dist/cli/{org-WCKS4ZDK.mjs → org-7WLA42NC.mjs} +4 -4
- package/dist/cli/{play-B6Y6ZCPV.js → play-UT3TLBM6.js} +283 -283
- package/dist/cli/{play-UYAZYXHV.mjs → play-WRHGX3WB.mjs} +10 -10
- package/dist/cli/{providers-IZVVGFAO.mjs → providers-UA2DJLBP.mjs} +4 -4
- package/dist/cli/{providers-U7BLFNT4.js → providers-ZR4SRGJF.js} +7 -7
- package/dist/cli/{quickstart-BY7N6GXZ.mjs → quickstart-IT46STSN.mjs} +3 -3
- package/dist/cli/{quickstart-MTNOTGIH.js → quickstart-QN5FXZWS.js} +5 -5
- package/dist/cli/search-INZD56I2.js +30 -0
- package/dist/cli/{search-SJXJKATQ.mjs → search-QKK5SGMV.mjs} +5 -5
- package/dist/cli/{secrets-C2FN2YMO.js → secrets-FX3IVBUP.js} +13 -13
- package/dist/cli/{secrets-IA6JBZEF.mjs → secrets-GWXM2FH4.mjs} +4 -4
- package/dist/cli/{sessions-MNK7ZR2Y.mjs → sessions-FDZ24XY4.mjs} +4 -4
- package/dist/cli/{sessions-HGJQTLZP.js → sessions-G6LCRYTC.js} +5 -5
- package/dist/cli/{settings-MRMSITAL.js → settings-6W42BCZE.js} +75 -75
- package/dist/cli/{settings-7UBMCVFK.mjs → settings-77ODFZUE.mjs} +8 -8
- package/dist/cli/{setup-MD4F55X4.mjs → setup-5OBB5UGB.mjs} +7 -7
- package/dist/cli/{setup-UX7KNUOW.js → setup-T3FF5IRN.js} +42 -42
- package/dist/cli/{skills-W7SAJDG6.mjs → skills-ADG7CBUP.mjs} +5 -5
- package/dist/cli/skills-ALCERVAB.js +29 -0
- package/dist/cli/tools-FNWIMNNK.js +44 -0
- package/dist/cli/{tools-VAWJLJAK.mjs → tools-OBZRYZ7K.mjs} +12 -12
- package/dist/cli/update-N7I4MXRK.js +31 -0
- package/dist/cli/{update-B2UOERFE.mjs → update-ODKOYFCA.mjs} +7 -7
- package/dist/cli/{workflow-3BYE3YFQ.mjs → workflow-RGKEVZSP.mjs} +5 -5
- package/dist/cli/{workflow-K2YJNF64.js → workflow-TBLNAOXJ.js} +15 -15
- package/dist/index.js +6 -6
- package/dist/index.mjs +3 -3
- package/dist/install-integrity.json +78 -78
- 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-AZVWLMZN.js +0 -40
- package/dist/cli/git-PANOVLJC.js +0 -24
- package/dist/cli/search-W7VXB4YP.js +0 -30
- package/dist/cli/skills-ZY4KFM33.js +0 -29
- package/dist/cli/tools-6G4PXAZ3.js +0 -44
- package/dist/cli/update-3D4R5FW6.js +0 -31
|
@@ -205,7 +205,7 @@ export const SDK_RELEASE = {
|
|
|
205
205
|
// getters keep their established compatibility behavior.
|
|
206
206
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
207
207
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
208
|
-
version: '0.3.
|
|
208
|
+
version: '0.3.445',
|
|
209
209
|
updateSummary: {
|
|
210
210
|
introducedIn: '0.3.90',
|
|
211
211
|
summary:
|
|
@@ -1102,6 +1102,27 @@ function appRuntimeRetryDelayMs(
|
|
|
1102
1102
|
return appRuntimeRetryDelaysForAction(action)[attempt - 1] ?? 1_000;
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
+
const APP_RUNTIME_RETRY_AFTER_MIN_SPREAD_MS = 1_000;
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Every caller shed at once gets the same retry-after. Spreading above it,
|
|
1109
|
+
* instead of taking max() with a smaller jittered step, keeps them from
|
|
1110
|
+
* retrying in lockstep.
|
|
1111
|
+
*/
|
|
1112
|
+
function appRuntimeRetrySleepMs(
|
|
1113
|
+
ladderDelayMs: number,
|
|
1114
|
+
retryAfterMs: number | null | undefined,
|
|
1115
|
+
): number {
|
|
1116
|
+
const jitteredMs = applyRetryJitter(ladderDelayMs);
|
|
1117
|
+
const floorMs = retryAfterMs ?? 0;
|
|
1118
|
+
if (floorMs <= jitteredMs) return jitteredMs;
|
|
1119
|
+
const spreadMs = Math.min(
|
|
1120
|
+
floorMs,
|
|
1121
|
+
Math.max(ladderDelayMs, APP_RUNTIME_RETRY_AFTER_MIN_SPREAD_MS),
|
|
1122
|
+
);
|
|
1123
|
+
return floorMs + Math.round(Math.random() * spreadMs);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1105
1126
|
function appRuntimeRetryAfterMs(
|
|
1106
1127
|
response: Response,
|
|
1107
1128
|
body: string,
|
|
@@ -1292,9 +1313,9 @@ async function retryAppRuntimeBodyTimeoutOrThrow(input: {
|
|
|
1292
1313
|
error: input.error,
|
|
1293
1314
|
})
|
|
1294
1315
|
) {
|
|
1295
|
-
const retryDelayMs =
|
|
1296
|
-
|
|
1297
|
-
input.retryAfterMs
|
|
1316
|
+
const retryDelayMs = appRuntimeRetrySleepMs(
|
|
1317
|
+
appRuntimeRetryDelayMs(input.action, input.attempt),
|
|
1318
|
+
input.retryAfterMs,
|
|
1298
1319
|
);
|
|
1299
1320
|
recordAppRuntimeRetryFailure({
|
|
1300
1321
|
telemetry: input.telemetry,
|
|
@@ -1860,9 +1881,9 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1860
1881
|
contentType: response.headers.get('content-type'),
|
|
1861
1882
|
})
|
|
1862
1883
|
) {
|
|
1863
|
-
const retryDelayMs =
|
|
1864
|
-
|
|
1865
|
-
appRuntimeRetryAfterMs(response, responseText)
|
|
1884
|
+
const retryDelayMs = appRuntimeRetrySleepMs(
|
|
1885
|
+
appRuntimeRetryDelayMs(body.action, attempt),
|
|
1886
|
+
appRuntimeRetryAfterMs(response, responseText),
|
|
1866
1887
|
);
|
|
1867
1888
|
recordAppRuntimeRetryFailure({
|
|
1868
1889
|
telemetry: retryTelemetry,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export type GatewayPostgresLane = 'ordinary' | 'commit';
|
|
2
2
|
|
|
3
|
+
export type GatewayPostgresAdmissionTenant = string | null;
|
|
4
|
+
export const GATEWAY_POSTGRES_MACHINE_TENANT: GatewayPostgresAdmissionTenant =
|
|
5
|
+
null;
|
|
6
|
+
|
|
3
7
|
export const GATEWAY_POSTGRES_MAX_ORDINARY_ACTIVE = 8;
|
|
4
8
|
export const GATEWAY_POSTGRES_MAX_TOTAL_ACTIVE = 10;
|
|
5
9
|
export const GATEWAY_POSTGRES_MAX_CONSECUTIVE_COMMIT_PRIORITY_GRANTS = 8;
|
|
@@ -16,8 +20,10 @@ export const GATEWAY_POSTGRES_MAX_WAITING_PER_LANE = 256;
|
|
|
16
20
|
export type GatewayPostgresAdmissionSnapshot = {
|
|
17
21
|
active: Record<GatewayPostgresLane, number> & { total: number };
|
|
18
22
|
waiting: Record<GatewayPostgresLane, number>;
|
|
23
|
+
waitingTenants: Record<GatewayPostgresLane, number>;
|
|
19
24
|
cancelled: number;
|
|
20
25
|
overloaded: number;
|
|
26
|
+
fairShareEvictions: number;
|
|
21
27
|
commitPriorityGrants: number;
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -30,7 +36,7 @@ export class GatewayPostgresAdmissionCancelledError extends Error {
|
|
|
30
36
|
|
|
31
37
|
/**
|
|
32
38
|
* Load shed at the admission boundary. It is only ever thrown before a waiter
|
|
33
|
-
* is
|
|
39
|
+
* is granted, so no pooled client, statement, or provider call exists for the
|
|
34
40
|
* rejected request. Callers must map it to a retryable back-pressure response.
|
|
35
41
|
*/
|
|
36
42
|
export class GatewayPostgresAdmissionOverloadedError extends Error {
|
|
@@ -38,24 +44,31 @@ export class GatewayPostgresAdmissionOverloadedError extends Error {
|
|
|
38
44
|
readonly lane: GatewayPostgresLane;
|
|
39
45
|
readonly waiting: number;
|
|
40
46
|
readonly limit: number;
|
|
47
|
+
readonly reason: 'queue_full' | 'fair_share_evicted';
|
|
41
48
|
|
|
42
49
|
constructor(input: {
|
|
43
50
|
lane: GatewayPostgresLane;
|
|
44
51
|
waiting: number;
|
|
45
52
|
limit: number;
|
|
53
|
+
reason?: 'queue_full' | 'fair_share_evicted';
|
|
46
54
|
}) {
|
|
55
|
+
const reason = input.reason ?? 'queue_full';
|
|
47
56
|
super(
|
|
48
|
-
|
|
57
|
+
reason === 'queue_full'
|
|
58
|
+
? `Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}).`
|
|
59
|
+
: `Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}); this waiter yielded its place to an org with a shorter queue.`,
|
|
49
60
|
);
|
|
50
61
|
this.name = 'GatewayPostgresAdmissionOverloadedError';
|
|
51
62
|
this.lane = input.lane;
|
|
52
63
|
this.waiting = input.waiting;
|
|
53
64
|
this.limit = input.limit;
|
|
65
|
+
this.reason = reason;
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
type Waiter = {
|
|
58
70
|
lane: GatewayPostgresLane;
|
|
71
|
+
tenant: GatewayPostgresAdmissionTenant;
|
|
59
72
|
signal: AbortSignal | null;
|
|
60
73
|
onAbort: (() => void) | null;
|
|
61
74
|
resolve: (release: () => void) => void;
|
|
@@ -63,15 +76,32 @@ type Waiter = {
|
|
|
63
76
|
settled: boolean;
|
|
64
77
|
};
|
|
65
78
|
|
|
79
|
+
type LaneQueue = {
|
|
80
|
+
ring: Map<GatewayPostgresAdmissionTenant, Waiter[]>;
|
|
81
|
+
size: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fair across orgs within each lane, so one org's burst cannot shed or starve
|
|
86
|
+
* every other org on this Machine.
|
|
87
|
+
*/
|
|
66
88
|
export class GatewayPostgresAdmission {
|
|
67
|
-
readonly #waiting: Record<GatewayPostgresLane,
|
|
68
|
-
ordinary:
|
|
69
|
-
commit:
|
|
89
|
+
readonly #waiting: Record<GatewayPostgresLane, LaneQueue> = {
|
|
90
|
+
ordinary: { ring: new Map(), size: 0 },
|
|
91
|
+
commit: { ring: new Map(), size: 0 },
|
|
92
|
+
};
|
|
93
|
+
readonly #activeByTenant: Record<
|
|
94
|
+
GatewayPostgresLane,
|
|
95
|
+
Map<GatewayPostgresAdmissionTenant, number>
|
|
96
|
+
> = {
|
|
97
|
+
ordinary: new Map(),
|
|
98
|
+
commit: new Map(),
|
|
70
99
|
};
|
|
71
100
|
#ordinaryActive = 0;
|
|
72
101
|
#commitActive = 0;
|
|
73
102
|
#cancelled = 0;
|
|
74
103
|
#overloaded = 0;
|
|
104
|
+
#fairShareEvictions = 0;
|
|
75
105
|
#commitPriorityGrants = 0;
|
|
76
106
|
#consecutiveCommitPriorityGrants = 0;
|
|
77
107
|
|
|
@@ -95,29 +125,27 @@ export class GatewayPostgresAdmission {
|
|
|
95
125
|
|
|
96
126
|
async acquire(
|
|
97
127
|
lane: GatewayPostgresLane,
|
|
98
|
-
options: {
|
|
128
|
+
options: {
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
tenant?: GatewayPostgresAdmissionTenant;
|
|
131
|
+
} = {},
|
|
99
132
|
): Promise<() => void> {
|
|
133
|
+
const tenant = options.tenant ?? GATEWAY_POSTGRES_MACHINE_TENANT;
|
|
100
134
|
if (options.signal?.aborted) {
|
|
101
135
|
this.#cancelled += 1;
|
|
102
136
|
throw new GatewayPostgresAdmissionCancelledError();
|
|
103
137
|
}
|
|
104
|
-
if (this.#canGrant(lane) && this.#waiting.commit.
|
|
105
|
-
return this.#grant(lane);
|
|
138
|
+
if (this.#canGrant(lane) && this.#waiting.commit.size === 0) {
|
|
139
|
+
return this.#grant(lane, tenant);
|
|
106
140
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (waiting >= limit) {
|
|
110
|
-
this.#overloaded += 1;
|
|
111
|
-
throw new GatewayPostgresAdmissionOverloadedError({
|
|
112
|
-
lane,
|
|
113
|
-
waiting,
|
|
114
|
-
limit,
|
|
115
|
-
});
|
|
141
|
+
if (this.#waiting[lane].size >= this.#maxWaitingPerLane()) {
|
|
142
|
+
this.#shedForFullLane(lane, tenant);
|
|
116
143
|
}
|
|
117
144
|
|
|
118
145
|
return await new Promise<() => void>((resolve, reject) => {
|
|
119
146
|
const waiter: Waiter = {
|
|
120
147
|
lane,
|
|
148
|
+
tenant,
|
|
121
149
|
signal: options.signal ?? null,
|
|
122
150
|
onAbort: null,
|
|
123
151
|
resolve,
|
|
@@ -130,7 +158,7 @@ export class GatewayPostgresAdmission {
|
|
|
130
158
|
reject(new GatewayPostgresAdmissionCancelledError());
|
|
131
159
|
};
|
|
132
160
|
waiter.signal?.addEventListener('abort', waiter.onAbort, { once: true });
|
|
133
|
-
this.#
|
|
161
|
+
this.#enqueue(waiter);
|
|
134
162
|
if (waiter.signal?.aborted) {
|
|
135
163
|
waiter.onAbort();
|
|
136
164
|
return;
|
|
@@ -147,11 +175,16 @@ export class GatewayPostgresAdmission {
|
|
|
147
175
|
total: this.#activeTotal(),
|
|
148
176
|
},
|
|
149
177
|
waiting: {
|
|
150
|
-
ordinary: this.#waiting.ordinary.
|
|
151
|
-
commit: this.#waiting.commit.
|
|
178
|
+
ordinary: this.#waiting.ordinary.size,
|
|
179
|
+
commit: this.#waiting.commit.size,
|
|
180
|
+
},
|
|
181
|
+
waitingTenants: {
|
|
182
|
+
ordinary: this.#waiting.ordinary.ring.size,
|
|
183
|
+
commit: this.#waiting.commit.ring.size,
|
|
152
184
|
},
|
|
153
185
|
cancelled: this.#cancelled,
|
|
154
186
|
overloaded: this.#overloaded,
|
|
187
|
+
fairShareEvictions: this.#fairShareEvictions,
|
|
155
188
|
commitPriorityGrants: this.#commitPriorityGrants,
|
|
156
189
|
};
|
|
157
190
|
}
|
|
@@ -183,15 +216,23 @@ export class GatewayPostgresAdmission {
|
|
|
183
216
|
);
|
|
184
217
|
}
|
|
185
218
|
|
|
186
|
-
#grant(
|
|
219
|
+
#grant(
|
|
220
|
+
lane: GatewayPostgresLane,
|
|
221
|
+
tenant: GatewayPostgresAdmissionTenant,
|
|
222
|
+
): () => void {
|
|
187
223
|
if (lane === 'ordinary') this.#ordinaryActive += 1;
|
|
188
224
|
else this.#commitActive += 1;
|
|
225
|
+
const activeByTenant = this.#activeByTenant[lane];
|
|
226
|
+
activeByTenant.set(tenant, (activeByTenant.get(tenant) ?? 0) + 1);
|
|
189
227
|
let released = false;
|
|
190
228
|
return () => {
|
|
191
229
|
if (released) return;
|
|
192
230
|
released = true;
|
|
193
231
|
if (lane === 'ordinary') this.#ordinaryActive -= 1;
|
|
194
232
|
else this.#commitActive -= 1;
|
|
233
|
+
const remaining = (activeByTenant.get(tenant) ?? 1) - 1;
|
|
234
|
+
if (remaining > 0) activeByTenant.set(tenant, remaining);
|
|
235
|
+
else activeByTenant.delete(tenant);
|
|
195
236
|
this.#drain();
|
|
196
237
|
};
|
|
197
238
|
}
|
|
@@ -199,19 +240,19 @@ export class GatewayPostgresAdmission {
|
|
|
199
240
|
#drain(): void {
|
|
200
241
|
while (this.#activeTotal() < this.#maxTotalActive()) {
|
|
201
242
|
const ordinaryCanRun =
|
|
202
|
-
this.#waiting.ordinary.
|
|
243
|
+
this.#waiting.ordinary.size > 0 &&
|
|
203
244
|
this.#ordinaryActive < this.#maxOrdinaryActive();
|
|
204
245
|
if (
|
|
205
246
|
ordinaryCanRun &&
|
|
206
|
-
this.#waiting.commit.
|
|
247
|
+
this.#waiting.commit.size > 0 &&
|
|
207
248
|
this.#consecutiveCommitPriorityGrants >=
|
|
208
249
|
GATEWAY_POSTGRES_MAX_CONSECUTIVE_COMMIT_PRIORITY_GRANTS
|
|
209
250
|
) {
|
|
210
251
|
this.#consecutiveCommitPriorityGrants = 0;
|
|
211
|
-
this.#resolve(this.#
|
|
252
|
+
this.#resolve(this.#dequeueLeastActiveTenant('ordinary')!);
|
|
212
253
|
continue;
|
|
213
254
|
}
|
|
214
|
-
const commit = this.#
|
|
255
|
+
const commit = this.#dequeueLeastActiveTenant('commit');
|
|
215
256
|
if (commit) {
|
|
216
257
|
if (ordinaryCanRun) {
|
|
217
258
|
this.#commitPriorityGrants += 1;
|
|
@@ -224,29 +265,110 @@ export class GatewayPostgresAdmission {
|
|
|
224
265
|
}
|
|
225
266
|
if (ordinaryCanRun) {
|
|
226
267
|
this.#consecutiveCommitPriorityGrants = 0;
|
|
227
|
-
this.#resolve(this.#
|
|
268
|
+
this.#resolve(this.#dequeueLeastActiveTenant('ordinary')!);
|
|
228
269
|
continue;
|
|
229
270
|
}
|
|
230
|
-
if (this.#waiting.ordinary.
|
|
271
|
+
if (this.#waiting.ordinary.size === 0) {
|
|
231
272
|
this.#consecutiveCommitPriorityGrants = 0;
|
|
232
273
|
}
|
|
233
274
|
return;
|
|
234
275
|
}
|
|
235
276
|
}
|
|
236
277
|
|
|
278
|
+
#enqueue(waiter: Waiter): void {
|
|
279
|
+
const queue = this.#waiting[waiter.lane];
|
|
280
|
+
const tenantQueue = queue.ring.get(waiter.tenant);
|
|
281
|
+
if (tenantQueue) tenantQueue.push(waiter);
|
|
282
|
+
else queue.ring.set(waiter.tenant, [waiter]);
|
|
283
|
+
queue.size += 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#shedForFullLane(
|
|
287
|
+
lane: GatewayPostgresLane,
|
|
288
|
+
tenant: GatewayPostgresAdmissionTenant,
|
|
289
|
+
): void {
|
|
290
|
+
const queue = this.#waiting[lane];
|
|
291
|
+
const waiting = queue.size;
|
|
292
|
+
const limit = this.#maxWaitingPerLane();
|
|
293
|
+
const longest = this.#longestTenantQueue(queue);
|
|
294
|
+
const own = queue.ring.get(tenant)?.length ?? 0;
|
|
295
|
+
this.#overloaded += 1;
|
|
296
|
+
if (!longest || own >= longest.length) {
|
|
297
|
+
throw new GatewayPostgresAdmissionOverloadedError({
|
|
298
|
+
lane,
|
|
299
|
+
waiting,
|
|
300
|
+
limit,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const evicted = longest[longest.length - 1]!;
|
|
304
|
+
this.#remove(evicted);
|
|
305
|
+
this.#fairShareEvictions += 1;
|
|
306
|
+
evicted.reject(
|
|
307
|
+
new GatewayPostgresAdmissionOverloadedError({
|
|
308
|
+
lane,
|
|
309
|
+
waiting,
|
|
310
|
+
limit,
|
|
311
|
+
reason: 'fair_share_evicted',
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#dequeueLeastActiveTenant(lane: GatewayPostgresLane): Waiter | undefined {
|
|
317
|
+
const queue = this.#waiting[lane];
|
|
318
|
+
const activeByTenant = this.#activeByTenant[lane];
|
|
319
|
+
let chosen: GatewayPostgresAdmissionTenant | undefined;
|
|
320
|
+
let chosenActive = Infinity;
|
|
321
|
+
for (const tenant of queue.ring.keys()) {
|
|
322
|
+
const active = activeByTenant.get(tenant) ?? 0;
|
|
323
|
+
if (active < chosenActive) {
|
|
324
|
+
chosen = tenant;
|
|
325
|
+
chosenActive = active;
|
|
326
|
+
if (active === 0) break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (chosen === undefined) return undefined;
|
|
330
|
+
const tenantQueue = queue.ring.get(chosen)!;
|
|
331
|
+
const waiter = tenantQueue.shift()!;
|
|
332
|
+
queue.size -= 1;
|
|
333
|
+
this.#rotateToBack(queue, chosen, tenantQueue);
|
|
334
|
+
return waiter;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#rotateToBack(
|
|
338
|
+
queue: LaneQueue,
|
|
339
|
+
tenant: GatewayPostgresAdmissionTenant,
|
|
340
|
+
tenantQueue: Waiter[],
|
|
341
|
+
): void {
|
|
342
|
+
queue.ring.delete(tenant);
|
|
343
|
+
if (tenantQueue.length > 0) queue.ring.set(tenant, tenantQueue);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#longestTenantQueue(queue: LaneQueue): Waiter[] | null {
|
|
347
|
+
let longest: Waiter[] | null = null;
|
|
348
|
+
for (const tenantQueue of queue.ring.values()) {
|
|
349
|
+
if (!longest || tenantQueue.length > longest.length) {
|
|
350
|
+
longest = tenantQueue;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return longest;
|
|
354
|
+
}
|
|
355
|
+
|
|
237
356
|
#resolve(waiter: Waiter): void {
|
|
238
357
|
waiter.settled = true;
|
|
239
358
|
this.#cleanup(waiter);
|
|
240
|
-
waiter.resolve(this.#grant(waiter.lane));
|
|
359
|
+
waiter.resolve(this.#grant(waiter.lane, waiter.tenant));
|
|
241
360
|
}
|
|
242
361
|
|
|
243
362
|
#remove(waiter: Waiter): boolean {
|
|
244
363
|
if (waiter.settled) return false;
|
|
245
364
|
const queue = this.#waiting[waiter.lane];
|
|
246
|
-
const
|
|
247
|
-
|
|
365
|
+
const tenantQueue = queue.ring.get(waiter.tenant);
|
|
366
|
+
const index = tenantQueue ? tenantQueue.indexOf(waiter) : -1;
|
|
367
|
+
if (!tenantQueue || index < 0) return false;
|
|
248
368
|
waiter.settled = true;
|
|
249
|
-
|
|
369
|
+
tenantQueue.splice(index, 1);
|
|
370
|
+
if (tenantQueue.length === 0) queue.ring.delete(waiter.tenant);
|
|
371
|
+
queue.size -= 1;
|
|
250
372
|
this.#cleanup(waiter);
|
|
251
373
|
return true;
|
|
252
374
|
}
|
package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
acquireRateStateViaAppRuntime,
|
|
3
|
+
AppRuntimeApiResponseError,
|
|
4
|
+
isAppRuntimeApiCapacityError,
|
|
3
5
|
penalizeRateStateViaAppRuntime,
|
|
4
6
|
releaseRateStateViaAppRuntime,
|
|
5
7
|
RateStateValidationUnavailableError,
|
|
@@ -141,6 +143,20 @@ function sleepWithAbort(
|
|
|
141
143
|
*/
|
|
142
144
|
const RATE_BLOCK_HORIZON_MS = 1_000;
|
|
143
145
|
const STORE_FAILURE_RETRY_DELAYS_MS = [100, 250] as const;
|
|
146
|
+
const CAPACITY_SHED_DEFAULT_RETRY_AFTER_MS = 1_000;
|
|
147
|
+
|
|
148
|
+
function capacityShedRetryAfterMs(error: unknown): number | null {
|
|
149
|
+
const shed =
|
|
150
|
+
isAppRuntimeApiCapacityError(error) ||
|
|
151
|
+
(error instanceof AppRuntimeApiResponseError &&
|
|
152
|
+
error.retryable &&
|
|
153
|
+
error.status === 429);
|
|
154
|
+
if (!shed) return null;
|
|
155
|
+
const retryAfterMs = (error as AppRuntimeApiResponseError).retryAfterMs;
|
|
156
|
+
return retryAfterMs && retryAfterMs > 0
|
|
157
|
+
? retryAfterMs
|
|
158
|
+
: CAPACITY_SHED_DEFAULT_RETRY_AFTER_MS;
|
|
159
|
+
}
|
|
144
160
|
/**
|
|
145
161
|
* Jitter fraction applied to a server-supplied `waitMs` so a synchronized wave
|
|
146
162
|
* of buckets that all hit granted=0 at the same instant re-probe at slightly
|
|
@@ -1140,6 +1156,22 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
|
|
|
1140
1156
|
// A provider hold is not a store failure. Preserve its deadline for
|
|
1141
1157
|
// the caller's durable suspension or non-durable error path.
|
|
1142
1158
|
if (error instanceof ProviderExhaustedError) throw error;
|
|
1159
|
+
// A capacity shed is back-pressure from a working store. Wait as for
|
|
1160
|
+
// a zero grant, then return so the acquirer re-checks its wait budget.
|
|
1161
|
+
const shedRetryAfterMs = capacityShedRetryAfterMs(error);
|
|
1162
|
+
if (shedRetryAfterMs !== null) {
|
|
1163
|
+
this.onStoreFailure({
|
|
1164
|
+
bucketId,
|
|
1165
|
+
error: normalizeError(error).message,
|
|
1166
|
+
});
|
|
1167
|
+
await this.sleep(
|
|
1168
|
+
Math.min(
|
|
1169
|
+
shedRetryAfterMs + Math.round(this.jitter() * shedRetryAfterMs),
|
|
1170
|
+
MAX_ACQUIRE_SLEEP_MS,
|
|
1171
|
+
),
|
|
1172
|
+
);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1143
1175
|
// Fail closed. A degraded rate-state store must not grant unlimited
|
|
1144
1176
|
// provider calls; after this bounded retry ladder the run fails loudly.
|
|
1145
1177
|
consecutiveFailures += 1;
|
|
@@ -14,8 +14,15 @@ import {
|
|
|
14
14
|
RuntimeSandboxProvider,
|
|
15
15
|
} from './runtime-sandbox-operation';
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The baseline when an organization has no current override. It sits above
|
|
19
|
+
* every ordinary organization's observed peak (at most 46 live sandboxes in the
|
|
20
|
+
* week before 2026-10-29) and bounds a burst: on 2026-10-29 one organization
|
|
21
|
+
* held 2,838 live sandboxes polling the receipt gateway. Keep in sync with the
|
|
22
|
+
* Convex control plane's DEFAULT_PLAY_SANDBOX_CONCURRENCY in
|
|
23
|
+
* packages/convex/auth.ts.
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_PLAY_SANDBOX_CONCURRENCY = 50;
|
|
19
26
|
export const MAX_PLAY_SANDBOX_CONCURRENCY = 250;
|
|
20
27
|
/** A crashed worker cannot retain capacity forever. */
|
|
21
28
|
export const RUNTIME_SANDBOX_CAPACITY_LEASE_MS = 50 * 60 * 1_000;
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
reportSetupCompleted,
|
|
14
14
|
resolvePendingAuthClaimForResume,
|
|
15
15
|
statusBannerLine
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-C54S4QPU.mjs";
|
|
17
|
+
import "./chunk-3OBHZADJ.mjs";
|
|
18
|
+
import "./chunk-ZRWX23RQ.mjs";
|
|
19
19
|
import "./chunk-4QF2K7TM.mjs";
|
|
20
20
|
import "./chunk-DLIRSHV6.mjs";
|
|
21
21
|
import "./chunk-HBL4PM5Q.mjs";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-6LVOC2HD.mjs";
|
|
23
23
|
import "./chunk-WZYKRD7T.mjs";
|
|
24
24
|
import "./chunk-KRD6H754.mjs";
|
|
25
25
|
export {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
var _chunkFC7LS3Y5js = require('./chunk-FC7LS3Y5.js');
|
|
17
|
+
require('./chunk-5JJ6JYTL.js');
|
|
18
|
+
require('./chunk-TABMEHIP.js');
|
|
19
|
+
require('./chunk-ALJJRZNJ.js');
|
|
20
|
+
require('./chunk-XNYIZMNQ.js');
|
|
21
|
+
require('./chunk-DIAWYWT5.js');
|
|
22
|
+
require('./chunk-EI2NN6HP.js');
|
|
23
|
+
require('./chunk-OG64RBAC.js');
|
|
24
|
+
require('./chunk-COGTRVWW.js');
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
exports.EXIT_WORKSPACE_MISMATCH = _chunkFC7LS3Y5js.EXIT_WORKSPACE_MISMATCH; exports.authStatusLine = _chunkFC7LS3Y5js.authStatusLine; exports.clearPendingClaim = _chunkFC7LS3Y5js.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkFC7LS3Y5js.clearSatisfiedPendingClaim; exports.handleRegister = _chunkFC7LS3Y5js.handleRegister; exports.handleStatus = _chunkFC7LS3Y5js.handleStatus; exports.handleWait = _chunkFC7LS3Y5js.handleWait; exports.httpJson = _chunkFC7LS3Y5js.httpJson; exports.readPendingAuthClaim = _chunkFC7LS3Y5js.readPendingAuthClaim; exports.readStatusBanner = _chunkFC7LS3Y5js.readStatusBanner; exports.registerAuthCommands = _chunkFC7LS3Y5js.registerAuthCommands; exports.reportSetupCompleted = _chunkFC7LS3Y5js.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkFC7LS3Y5js.resolvePendingAuthClaimForResume; exports.statusBannerLine = _chunkFC7LS3Y5js.statusBannerLine;
|