deepline 0.2.55 → 0.2.57
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/client.ts +14 -0
- package/dist/bundling-sources/sdk/src/http.ts +19 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/stream-reconnect.ts +6 -0
- package/dist/bundling-sources/sdk/src/types.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +146 -12
- package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +6 -3
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1631 -748
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +81 -52
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +146 -6
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +56 -22
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +14 -11
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +21 -2
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +81 -21
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +27 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +49 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +96 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +341 -67
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +17 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +17 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +16 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +25 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +80 -1
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +113 -14
- package/dist/bundling-sources/shared_libs/plays/play-exports.ts +53 -4
- package/dist/bundling-sources/shared_libs/security/safe-fetch.ts +9 -0
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +85 -48
- package/dist/cli/index.js +429 -54
- package/dist/cli/index.mjs +409 -28
- package/dist/{compiler-manifest-Bl8kmLx9.d.mts → compiler-manifest-TgaC4DeD.d.mts} +13 -0
- package/dist/{compiler-manifest-Bl8kmLx9.d.ts → compiler-manifest-TgaC4DeD.d.ts} +13 -0
- package/dist/index.d.mts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +29 -2
- package/dist/index.mjs +29 -2
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +78 -18
- package/package.json +1 -1
|
@@ -275,10 +275,25 @@ type RuntimeSheetPreparedRowDisposition = {
|
|
|
275
275
|
disposition: RuntimeSheetPrepareDisposition;
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
// A failed connect retires one cached generation. It cannot end that pool
|
|
279
|
+
// until every request that already borrowed the generation has returned.
|
|
280
|
+
type CachedRuntimePostgresPool = {
|
|
281
|
+
pool: RuntimePool;
|
|
282
|
+
borrowers: number;
|
|
283
|
+
retired: boolean;
|
|
284
|
+
endPromise: Promise<void> | null;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
type RuntimePostgresPoolLease = {
|
|
288
|
+
pool: RuntimePool;
|
|
289
|
+
cached: CachedRuntimePostgresPool | null;
|
|
290
|
+
release(): Promise<void>;
|
|
291
|
+
};
|
|
292
|
+
|
|
278
293
|
type RuntimeSheetDatasetMode = 'upsert' | 'net_new';
|
|
279
294
|
const dbSessionCache = new Map<string, DbSessionCacheEntry>();
|
|
280
295
|
const dbSessionInFlight = new Map<string, Promise<CreateDbSessionResponse>>();
|
|
281
|
-
const postgresPools = new Map<string,
|
|
296
|
+
const postgresPools = new Map<string, CachedRuntimePostgresPool>();
|
|
282
297
|
const runtimePostgresAdmissions = new Map<string, RuntimePostgresAdmission>();
|
|
283
298
|
const runtimeWorkReceiptEnsureCache = new Map<string, Promise<void>>();
|
|
284
299
|
const runtimeSheetEnsureCache = new Map<
|
|
@@ -331,6 +346,14 @@ const RUNTIME_API_REQUEST_TIMEOUT_MS = 30_000;
|
|
|
331
346
|
// writes. Keep the caller alive long enough for the FIFO admission queue to
|
|
332
347
|
// make progress instead of aborting a healthy queued start after 30 seconds.
|
|
333
348
|
const RUNTIME_SHEET_ADMISSION_REQUEST_TIMEOUT_MS = 10 * 60_000;
|
|
349
|
+
const RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS = 75_000;
|
|
350
|
+
// Server admission is bounded to 30s and the single connection attempt to
|
|
351
|
+
// 10s. Keep the transport outside that envelope so it never retries while the
|
|
352
|
+
// first server-side write can still be active.
|
|
353
|
+
const RUNTIME_SHEET_COMPLETION_REQUEST_TIMEOUT_MS = 150_000;
|
|
354
|
+
const RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS = 180_000;
|
|
355
|
+
const RUNTIME_SHEET_COMPLETION_STATEMENT_TIMEOUT_MS = 60_000;
|
|
356
|
+
const RUNTIME_POSTGRES_SHEET_ADMISSION_TIMEOUT_MS = 30_000;
|
|
334
357
|
const RUNTIME_POSTGRES_PREWARM_MAX_ATTEMPTS = 4;
|
|
335
358
|
const RUNTIME_POSTGRES_PREWARM_RETRY_DELAYS_MS = [250, 750, 1_500] as const;
|
|
336
359
|
const RUNTIME_POSTGRES_CONNECT_MAX_ATTEMPTS = 4;
|
|
@@ -624,13 +647,21 @@ async function postRuntimeApi<TResponse>(
|
|
|
624
647
|
context.vercelProtectionBypassToken,
|
|
625
648
|
);
|
|
626
649
|
const maxAttempts = runtimeApiMaxAttempts(body.action);
|
|
650
|
+
const operationStartedAt = Date.now();
|
|
627
651
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
628
652
|
let response: Response | null = null;
|
|
629
653
|
let parsed: Record<string, unknown> | null = null;
|
|
630
654
|
const abortController = new AbortController();
|
|
655
|
+
const operationRemainingMs = runtimeApiOperationRemainingMs(
|
|
656
|
+
body.action,
|
|
657
|
+
operationStartedAt,
|
|
658
|
+
);
|
|
659
|
+
if (operationRemainingMs <= 0) {
|
|
660
|
+
throw runtimeApiTotalDeadlineError(body.action);
|
|
661
|
+
}
|
|
631
662
|
const timeout = setTimeout(
|
|
632
663
|
() => abortController.abort(),
|
|
633
|
-
runtimeApiRequestTimeoutMs(body.action),
|
|
664
|
+
Math.min(runtimeApiRequestTimeoutMs(body.action), operationRemainingMs),
|
|
634
665
|
);
|
|
635
666
|
try {
|
|
636
667
|
response = await fetch(url, {
|
|
@@ -661,8 +692,18 @@ async function postRuntimeApi<TResponse>(
|
|
|
661
692
|
if (context.abortSignal?.aborted) {
|
|
662
693
|
throw context.abortSignal.reason ?? error;
|
|
663
694
|
}
|
|
695
|
+
if (
|
|
696
|
+
runtimeApiOperationRemainingMs(body.action, operationStartedAt) <= 0
|
|
697
|
+
) {
|
|
698
|
+
throw runtimeApiTotalDeadlineError(body.action);
|
|
699
|
+
}
|
|
664
700
|
if (attempt < maxAttempts) {
|
|
665
|
-
await sleepRuntimeApiRetry(
|
|
701
|
+
await sleepRuntimeApiRetry({
|
|
702
|
+
action: body.action,
|
|
703
|
+
attempt,
|
|
704
|
+
operationStartedAt,
|
|
705
|
+
abortSignal: context.abortSignal ?? undefined,
|
|
706
|
+
});
|
|
666
707
|
continue;
|
|
667
708
|
}
|
|
668
709
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -686,7 +727,12 @@ async function postRuntimeApi<TResponse>(
|
|
|
686
727
|
// every runtime action returns a JSON object and runtime writes are
|
|
687
728
|
// already retry-safe across the equivalent fetch-failed boundary.
|
|
688
729
|
if (attempt < maxAttempts) {
|
|
689
|
-
await sleepRuntimeApiRetry(
|
|
730
|
+
await sleepRuntimeApiRetry({
|
|
731
|
+
action: body.action,
|
|
732
|
+
attempt,
|
|
733
|
+
operationStartedAt,
|
|
734
|
+
abortSignal: context.abortSignal ?? undefined,
|
|
735
|
+
});
|
|
690
736
|
continue;
|
|
691
737
|
}
|
|
692
738
|
const requestId = response.headers.get('x-deepline-request-id');
|
|
@@ -740,7 +786,13 @@ async function postRuntimeApi<TResponse>(
|
|
|
740
786
|
`${typeof parsed?.error === 'string' ? parsed.error : ''}\n${details ?? ''}`,
|
|
741
787
|
));
|
|
742
788
|
if (shouldRetryRuntimeResponse && attempt < maxAttempts) {
|
|
743
|
-
await sleepRuntimeApiRetry(
|
|
789
|
+
await sleepRuntimeApiRetry({
|
|
790
|
+
action: body.action,
|
|
791
|
+
attempt,
|
|
792
|
+
retryAfterMs,
|
|
793
|
+
operationStartedAt,
|
|
794
|
+
abortSignal: context.abortSignal ?? undefined,
|
|
795
|
+
});
|
|
744
796
|
continue;
|
|
745
797
|
}
|
|
746
798
|
|
|
@@ -786,22 +838,75 @@ function runtimeApiRetryDelayMs(
|
|
|
786
838
|
function runtimeApiRequestTimeoutMs(
|
|
787
839
|
action: RuntimeApiActionRequest['action'],
|
|
788
840
|
): number {
|
|
789
|
-
|
|
790
|
-
|
|
841
|
+
if (action === 'runtime_sheet_complete_map_rows') {
|
|
842
|
+
return RUNTIME_SHEET_COMPLETION_REQUEST_TIMEOUT_MS;
|
|
843
|
+
}
|
|
844
|
+
return action === 'runtime_sheet_start'
|
|
791
845
|
? RUNTIME_SHEET_ADMISSION_REQUEST_TIMEOUT_MS
|
|
792
846
|
: RUNTIME_API_REQUEST_TIMEOUT_MS;
|
|
793
847
|
}
|
|
794
848
|
|
|
795
|
-
|
|
849
|
+
function runtimeApiOperationRemainingMs(
|
|
796
850
|
action: RuntimeApiActionRequest['action'],
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
851
|
+
startedAt: number,
|
|
852
|
+
): number {
|
|
853
|
+
return action === 'runtime_sheet_complete_map_rows'
|
|
854
|
+
? Math.max(
|
|
855
|
+
0,
|
|
856
|
+
RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS - (Date.now() - startedAt),
|
|
857
|
+
)
|
|
858
|
+
: Number.POSITIVE_INFINITY;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function runtimeApiTotalDeadlineError(
|
|
862
|
+
action: RuntimeApiActionRequest['action'],
|
|
863
|
+
): Error {
|
|
864
|
+
return new Error(
|
|
865
|
+
`Runtime API ${action} exceeded its ` +
|
|
866
|
+
`${RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS}ms total deadline.`,
|
|
802
867
|
);
|
|
803
868
|
}
|
|
804
869
|
|
|
870
|
+
async function sleepRuntimeApiRetry(input: {
|
|
871
|
+
action: RuntimeApiActionRequest['action'];
|
|
872
|
+
attempt: number;
|
|
873
|
+
retryAfterMs?: number;
|
|
874
|
+
operationStartedAt: number;
|
|
875
|
+
abortSignal?: AbortSignal;
|
|
876
|
+
}): Promise<void> {
|
|
877
|
+
const requestedDelayMs = runtimeApiRetryDelayMs(
|
|
878
|
+
input.action,
|
|
879
|
+
input.attempt,
|
|
880
|
+
input.retryAfterMs,
|
|
881
|
+
);
|
|
882
|
+
const operationRemainingMs = runtimeApiOperationRemainingMs(
|
|
883
|
+
input.action,
|
|
884
|
+
input.operationStartedAt,
|
|
885
|
+
);
|
|
886
|
+
if (operationRemainingMs <= 0) {
|
|
887
|
+
throw runtimeApiTotalDeadlineError(input.action);
|
|
888
|
+
}
|
|
889
|
+
const delayMs = Math.min(requestedDelayMs, operationRemainingMs);
|
|
890
|
+
await new Promise<void>((resolve, reject) => {
|
|
891
|
+
const timeout = setTimeout(() => {
|
|
892
|
+
input.abortSignal?.removeEventListener('abort', onAbort);
|
|
893
|
+
resolve();
|
|
894
|
+
}, delayMs);
|
|
895
|
+
const onAbort = () => {
|
|
896
|
+
clearTimeout(timeout);
|
|
897
|
+
reject(input.abortSignal?.reason ?? new Error('Runtime API aborted.'));
|
|
898
|
+
};
|
|
899
|
+
if (input.abortSignal?.aborted) {
|
|
900
|
+
onAbort();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
input.abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
904
|
+
});
|
|
905
|
+
if (requestedDelayMs >= operationRemainingMs) {
|
|
906
|
+
throw runtimeApiTotalDeadlineError(input.action);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
805
910
|
function runtimeApiRetryDelaysForAction(
|
|
806
911
|
action: RuntimeApiActionRequest['action'],
|
|
807
912
|
): readonly number[] {
|
|
@@ -1708,10 +1813,17 @@ async function connectRuntimePostgresPool(
|
|
|
1708
1813
|
let released = false;
|
|
1709
1814
|
return {
|
|
1710
1815
|
query: (text, params) => client.query(text, params),
|
|
1711
|
-
release: () => {
|
|
1816
|
+
release: (destroy = false) => {
|
|
1712
1817
|
if (released) return;
|
|
1713
1818
|
released = true;
|
|
1714
|
-
client.release();
|
|
1819
|
+
client.release(destroy);
|
|
1820
|
+
releaseAdmission();
|
|
1821
|
+
},
|
|
1822
|
+
destroy: (error) => {
|
|
1823
|
+
if (released) return;
|
|
1824
|
+
released = true;
|
|
1825
|
+
if (client.destroy) client.destroy(error);
|
|
1826
|
+
else client.release(true);
|
|
1715
1827
|
releaseAdmission();
|
|
1716
1828
|
},
|
|
1717
1829
|
};
|
|
@@ -1753,15 +1865,13 @@ function getRuntimePostgresAdmission(
|
|
|
1753
1865
|
receipts: RUNTIME_POSTGRES_RECEIPT_ADMISSION_MAX_QUEUED,
|
|
1754
1866
|
sheets: RUNTIME_POSTGRES_SHEET_ADMISSION_MAX_QUEUED,
|
|
1755
1867
|
},
|
|
1756
|
-
//
|
|
1757
|
-
//
|
|
1758
|
-
//
|
|
1759
|
-
//
|
|
1760
|
-
// a receipt can follow the ordinary retry/recovery path without holding a
|
|
1761
|
-
// row's data-plane transaction open.
|
|
1868
|
+
// Capacity is backpressure, not a sheet-persistence failure. Bound both
|
|
1869
|
+
// queues so the gateway can return typed capacity before an HTTP caller
|
|
1870
|
+
// times out. The runner then parks the same frozen write and retries it;
|
|
1871
|
+
// it never leaves an invisible request queued behind a dead client.
|
|
1762
1872
|
acquireTimeoutMs: {
|
|
1763
1873
|
receipts: RUNTIME_POSTGRES_RECEIPT_ADMISSION_TIMEOUT_MS,
|
|
1764
|
-
sheets:
|
|
1874
|
+
sheets: RUNTIME_POSTGRES_SHEET_ADMISSION_TIMEOUT_MS,
|
|
1765
1875
|
},
|
|
1766
1876
|
});
|
|
1767
1877
|
runtimePostgresAdmissions.set(postgresUrl, admission);
|
|
@@ -1774,46 +1884,149 @@ export function runtimePostgresAdmissionTelemetry(
|
|
|
1774
1884
|
return runtimePostgresAdmissions.get(postgresUrl)?.snapshot() ?? null;
|
|
1775
1885
|
}
|
|
1776
1886
|
|
|
1777
|
-
function
|
|
1887
|
+
function endRetiredRuntimePostgresPool(
|
|
1888
|
+
cached: CachedRuntimePostgresPool,
|
|
1889
|
+
): Promise<void> {
|
|
1890
|
+
if (!cached.retired || cached.borrowers > 0) {
|
|
1891
|
+
return Promise.resolve();
|
|
1892
|
+
}
|
|
1893
|
+
cached.endPromise ??= Promise.resolve()
|
|
1894
|
+
.then(() => cached.pool.end())
|
|
1895
|
+
.catch(() => {});
|
|
1896
|
+
return cached.endPromise;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
async function retireRuntimePostgresPool(
|
|
1900
|
+
poolKey: string,
|
|
1901
|
+
cached: CachedRuntimePostgresPool,
|
|
1902
|
+
): Promise<void> {
|
|
1903
|
+
if (postgresPools.get(poolKey) === cached) {
|
|
1904
|
+
postgresPools.delete(poolKey);
|
|
1905
|
+
}
|
|
1906
|
+
cached.retired = true;
|
|
1907
|
+
await endRetiredRuntimePostgresPool(cached);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function getPostgresPoolLease(
|
|
1778
1911
|
postgresUrl: string,
|
|
1779
1912
|
lane: RuntimePostgresLane,
|
|
1780
1913
|
cachePool = true,
|
|
1781
|
-
):
|
|
1914
|
+
): RuntimePostgresPoolLease {
|
|
1782
1915
|
if (!cachePool) {
|
|
1783
|
-
return
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1916
|
+
return {
|
|
1917
|
+
pool: createRuntimePool({
|
|
1918
|
+
connectionString: postgresUrl,
|
|
1919
|
+
maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
|
|
1920
|
+
idleTimeoutMs: 0,
|
|
1921
|
+
connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
|
|
1922
|
+
}),
|
|
1923
|
+
cached: null,
|
|
1924
|
+
release: async () => {},
|
|
1925
|
+
};
|
|
1789
1926
|
}
|
|
1790
1927
|
const poolKey = runtimePostgresPoolKey(postgresUrl, lane);
|
|
1791
|
-
|
|
1792
|
-
if (
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1928
|
+
let cached = postgresPools.get(poolKey);
|
|
1929
|
+
if (!cached) {
|
|
1930
|
+
cached = {
|
|
1931
|
+
pool: createRuntimePool({
|
|
1932
|
+
connectionString: postgresUrl,
|
|
1933
|
+
maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
|
|
1934
|
+
idleTimeoutMs: 15_000,
|
|
1935
|
+
connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
|
|
1936
|
+
}),
|
|
1937
|
+
borrowers: 0,
|
|
1938
|
+
retired: false,
|
|
1939
|
+
endPromise: null,
|
|
1940
|
+
};
|
|
1941
|
+
postgresPools.set(poolKey, cached);
|
|
1942
|
+
}
|
|
1943
|
+
cached.borrowers += 1;
|
|
1944
|
+
let released = false;
|
|
1945
|
+
return {
|
|
1946
|
+
pool: cached.pool,
|
|
1947
|
+
cached,
|
|
1948
|
+
release: async () => {
|
|
1949
|
+
if (released) return;
|
|
1950
|
+
released = true;
|
|
1951
|
+
cached.borrowers -= 1;
|
|
1952
|
+
await endRetiredRuntimePostgresPool(cached);
|
|
1953
|
+
},
|
|
1954
|
+
};
|
|
1803
1955
|
}
|
|
1804
1956
|
|
|
1805
1957
|
async function resetRuntimePostgresPool(postgresUrl: string): Promise<void> {
|
|
1806
|
-
const
|
|
1958
|
+
const retirements = (['receipts', 'sheets'] as const)
|
|
1807
1959
|
.map((lane) => runtimePostgresPoolKey(postgresUrl, lane))
|
|
1808
1960
|
.map((key) => {
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
return
|
|
1961
|
+
const cached = postgresPools.get(key);
|
|
1962
|
+
if (!cached) return null;
|
|
1963
|
+
return retireRuntimePostgresPool(key, cached);
|
|
1812
1964
|
})
|
|
1813
|
-
.filter((
|
|
1814
|
-
await Promise.all(
|
|
1815
|
-
|
|
1816
|
-
|
|
1965
|
+
.filter((retirement): retirement is Promise<void> => retirement !== null);
|
|
1966
|
+
await Promise.all(retirements);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
async function awaitRuntimePostgresOperation<T>(input: {
|
|
1970
|
+
client: RuntimePoolClient;
|
|
1971
|
+
deadlineAt: number | null;
|
|
1972
|
+
operation: Promise<T>;
|
|
1973
|
+
signal?: AbortSignal | null;
|
|
1974
|
+
}): Promise<T> {
|
|
1975
|
+
if (input.deadlineAt === null && !input.signal) return await input.operation;
|
|
1976
|
+
const remainingMs =
|
|
1977
|
+
input.deadlineAt === null
|
|
1978
|
+
? null
|
|
1979
|
+
: Math.max(0, input.deadlineAt - Date.now());
|
|
1980
|
+
if (remainingMs === 0 || input.signal?.aborted) {
|
|
1981
|
+
const error =
|
|
1982
|
+
input.signal?.reason ??
|
|
1983
|
+
new Error('Runtime Postgres operation exceeded its total deadline.');
|
|
1984
|
+
if (input.client.destroy) input.client.destroy(error);
|
|
1985
|
+
else input.client.release(true);
|
|
1986
|
+
throw error;
|
|
1987
|
+
}
|
|
1988
|
+
return await new Promise<T>((resolve, reject) => {
|
|
1989
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
1990
|
+
const cleanup = () => {
|
|
1991
|
+
if (timeout) clearTimeout(timeout);
|
|
1992
|
+
input.signal?.removeEventListener('abort', onAbort);
|
|
1993
|
+
};
|
|
1994
|
+
const rejectAndDestroy = (error: unknown) => {
|
|
1995
|
+
const normalizedError =
|
|
1996
|
+
error instanceof Error ? error : new Error(String(error));
|
|
1997
|
+
if (input.client.destroy) input.client.destroy(normalizedError);
|
|
1998
|
+
else input.client.release(true);
|
|
1999
|
+
cleanup();
|
|
2000
|
+
reject(error);
|
|
2001
|
+
};
|
|
2002
|
+
const onAbort = () =>
|
|
2003
|
+
rejectAndDestroy(
|
|
2004
|
+
input.signal?.reason ??
|
|
2005
|
+
new Error('Runtime Postgres operation aborted.'),
|
|
2006
|
+
);
|
|
2007
|
+
if (remainingMs !== null) {
|
|
2008
|
+
timeout = setTimeout(
|
|
2009
|
+
() =>
|
|
2010
|
+
rejectAndDestroy(
|
|
2011
|
+
new Error(
|
|
2012
|
+
`Runtime Postgres operation exceeded its ${remainingMs}ms remaining deadline.`,
|
|
2013
|
+
),
|
|
2014
|
+
),
|
|
2015
|
+
remainingMs,
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
input.signal?.addEventListener('abort', onAbort, { once: true });
|
|
2019
|
+
input.operation.then(
|
|
2020
|
+
(value) => {
|
|
2021
|
+
cleanup();
|
|
2022
|
+
resolve(value);
|
|
2023
|
+
},
|
|
2024
|
+
(error) => {
|
|
2025
|
+
cleanup();
|
|
2026
|
+
reject(error);
|
|
2027
|
+
},
|
|
2028
|
+
);
|
|
2029
|
+
});
|
|
1817
2030
|
}
|
|
1818
2031
|
|
|
1819
2032
|
async function withRuntimePostgres<T>(
|
|
@@ -1825,12 +2038,15 @@ async function withRuntimePostgres<T>(
|
|
|
1825
2038
|
options: {
|
|
1826
2039
|
cachePool?: boolean;
|
|
1827
2040
|
maxConnectAttempts?: number;
|
|
2041
|
+
operationTimeoutMs?: number;
|
|
1828
2042
|
signal?: AbortSignal | null;
|
|
1829
2043
|
} = {},
|
|
1830
2044
|
): Promise<T> {
|
|
1831
2045
|
let client: RuntimePoolClient | null = null;
|
|
1832
2046
|
let requestLocalPool: RuntimePool | null = null;
|
|
2047
|
+
let poolLease: RuntimePostgresPoolLease | null = null;
|
|
1833
2048
|
let roleTransactionStarted = false;
|
|
2049
|
+
let operationDeadlineAt: number | null = null;
|
|
1834
2050
|
const cachePool =
|
|
1835
2051
|
(options.cachePool ?? true) && canReuseRuntimePostgresPoolsAcrossRequests();
|
|
1836
2052
|
const maxConnectAttempts =
|
|
@@ -1841,7 +2057,8 @@ async function withRuntimePostgres<T>(
|
|
|
1841
2057
|
const admission = getRuntimePostgresAdmission(session.postgresUrl);
|
|
1842
2058
|
for (let attempt = 1; attempt <= maxConnectAttempts; attempt += 1) {
|
|
1843
2059
|
try {
|
|
1844
|
-
|
|
2060
|
+
poolLease = getPostgresPoolLease(session.postgresUrl, lane, cachePool);
|
|
2061
|
+
const pool = poolLease.pool;
|
|
1845
2062
|
if (!cachePool) {
|
|
1846
2063
|
requestLocalPool = pool;
|
|
1847
2064
|
}
|
|
@@ -1852,18 +2069,37 @@ async function withRuntimePostgres<T>(
|
|
|
1852
2069
|
session.postgresUrl,
|
|
1853
2070
|
options.signal,
|
|
1854
2071
|
);
|
|
2072
|
+
operationDeadlineAt =
|
|
2073
|
+
options.operationTimeoutMs === undefined
|
|
2074
|
+
? null
|
|
2075
|
+
: Date.now() + Math.max(1, Math.floor(options.operationTimeoutMs));
|
|
1855
2076
|
if (session.executionRole) {
|
|
1856
|
-
await
|
|
2077
|
+
await awaitRuntimePostgresOperation({
|
|
2078
|
+
client,
|
|
2079
|
+
deadlineAt: operationDeadlineAt,
|
|
2080
|
+
operation: client.query('BEGIN'),
|
|
2081
|
+
signal: options.signal,
|
|
2082
|
+
});
|
|
1857
2083
|
roleTransactionStarted = true;
|
|
1858
|
-
await
|
|
1859
|
-
|
|
1860
|
-
|
|
2084
|
+
await awaitRuntimePostgresOperation({
|
|
2085
|
+
client,
|
|
2086
|
+
deadlineAt: operationDeadlineAt,
|
|
2087
|
+
operation: client.query(
|
|
2088
|
+
`SET LOCAL ROLE ${quoteIdentifier(session.executionRole)}`,
|
|
2089
|
+
),
|
|
2090
|
+
signal: options.signal,
|
|
2091
|
+
});
|
|
1861
2092
|
}
|
|
1862
2093
|
break;
|
|
1863
2094
|
} catch (error) {
|
|
1864
2095
|
if (client) {
|
|
1865
2096
|
if (roleTransactionStarted) {
|
|
1866
|
-
await
|
|
2097
|
+
await awaitRuntimePostgresOperation({
|
|
2098
|
+
client,
|
|
2099
|
+
deadlineAt: operationDeadlineAt,
|
|
2100
|
+
operation: client.query('ROLLBACK'),
|
|
2101
|
+
signal: options.signal,
|
|
2102
|
+
}).catch(() => {});
|
|
1867
2103
|
roleTransactionStarted = false;
|
|
1868
2104
|
}
|
|
1869
2105
|
client.release();
|
|
@@ -1875,17 +2111,15 @@ async function withRuntimePostgres<T>(
|
|
|
1875
2111
|
// the backpressure in the first place.
|
|
1876
2112
|
const preserveSharedPool =
|
|
1877
2113
|
cachePool && isRuntimePostgresAdmissionError(error);
|
|
1878
|
-
if (cachePool && !preserveSharedPool) {
|
|
2114
|
+
if (cachePool && !preserveSharedPool && poolLease?.cached) {
|
|
1879
2115
|
const poolKey = runtimePostgresPoolKey(session.postgresUrl, lane);
|
|
1880
|
-
|
|
1881
|
-
postgresPools.delete(poolKey);
|
|
1882
|
-
if (pool) {
|
|
1883
|
-
await Promise.resolve(pool.end()).catch(() => {});
|
|
1884
|
-
}
|
|
2116
|
+
await retireRuntimePostgresPool(poolKey, poolLease.cached);
|
|
1885
2117
|
} else if (requestLocalPool) {
|
|
1886
2118
|
await Promise.resolve(requestLocalPool.end()).catch(() => {});
|
|
1887
2119
|
requestLocalPool = null;
|
|
1888
2120
|
}
|
|
2121
|
+
await poolLease?.release();
|
|
2122
|
+
poolLease = null;
|
|
1889
2123
|
if (
|
|
1890
2124
|
attempt >= maxConnectAttempts ||
|
|
1891
2125
|
!isTransientRuntimePostgresConnectionError(error)
|
|
@@ -1904,20 +2138,36 @@ async function withRuntimePostgres<T>(
|
|
|
1904
2138
|
throw new Error('Runtime Postgres connection was not acquired.');
|
|
1905
2139
|
}
|
|
1906
2140
|
try {
|
|
1907
|
-
const result = await
|
|
2141
|
+
const result = await awaitRuntimePostgresOperation({
|
|
2142
|
+
client,
|
|
2143
|
+
deadlineAt: operationDeadlineAt,
|
|
2144
|
+
operation: fn(client, { active: roleTransactionStarted }),
|
|
2145
|
+
signal: options.signal,
|
|
2146
|
+
});
|
|
1908
2147
|
if (roleTransactionStarted) {
|
|
1909
|
-
await
|
|
2148
|
+
await awaitRuntimePostgresOperation({
|
|
2149
|
+
client,
|
|
2150
|
+
deadlineAt: operationDeadlineAt,
|
|
2151
|
+
operation: client.query('COMMIT'),
|
|
2152
|
+
signal: options.signal,
|
|
2153
|
+
});
|
|
1910
2154
|
roleTransactionStarted = false;
|
|
1911
2155
|
}
|
|
1912
2156
|
return result;
|
|
1913
2157
|
} catch (error) {
|
|
1914
2158
|
if (roleTransactionStarted) {
|
|
1915
|
-
await
|
|
2159
|
+
await awaitRuntimePostgresOperation({
|
|
2160
|
+
client,
|
|
2161
|
+
deadlineAt: operationDeadlineAt,
|
|
2162
|
+
operation: client.query('ROLLBACK'),
|
|
2163
|
+
signal: options.signal,
|
|
2164
|
+
}).catch(() => {});
|
|
1916
2165
|
roleTransactionStarted = false;
|
|
1917
2166
|
}
|
|
1918
2167
|
throw error;
|
|
1919
2168
|
} finally {
|
|
1920
2169
|
client.release();
|
|
2170
|
+
await poolLease?.release();
|
|
1921
2171
|
if (requestLocalPool) {
|
|
1922
2172
|
await Promise.resolve(requestLocalPool.end()).catch(() => {});
|
|
1923
2173
|
}
|
|
@@ -2083,6 +2333,7 @@ async function withRuntimeSheetQueryClient<T>(
|
|
|
2083
2333
|
tableNamespace: string;
|
|
2084
2334
|
sheetContract: PlaySheetContract;
|
|
2085
2335
|
transactional: boolean;
|
|
2336
|
+
statementTimeoutMs?: number;
|
|
2086
2337
|
timings?: RuntimeSheetTiming[];
|
|
2087
2338
|
},
|
|
2088
2339
|
operation: (client: RuntimeQueryClient) => Promise<T>,
|
|
@@ -2092,7 +2343,11 @@ async function withRuntimeSheetQueryClient<T>(
|
|
|
2092
2343
|
context,
|
|
2093
2344
|
input,
|
|
2094
2345
|
async () => {
|
|
2095
|
-
if (
|
|
2346
|
+
if (
|
|
2347
|
+
!input.transactional &&
|
|
2348
|
+
input.statementTimeoutMs === undefined &&
|
|
2349
|
+
isRuntimeOneShotQueryFactoryRegistered()
|
|
2350
|
+
) {
|
|
2096
2351
|
const operationStartedAt = Date.now();
|
|
2097
2352
|
return await withRuntimeOneShotPostgres(session, operation).finally(
|
|
2098
2353
|
() => {
|
|
@@ -2107,9 +2362,16 @@ async function withRuntimeSheetQueryClient<T>(
|
|
|
2107
2362
|
return await withRuntimePostgres(
|
|
2108
2363
|
session,
|
|
2109
2364
|
async (client, transaction) => {
|
|
2110
|
-
const managesTransaction =
|
|
2365
|
+
const managesTransaction =
|
|
2366
|
+
(input.transactional || input.statementTimeoutMs !== undefined) &&
|
|
2367
|
+
!transaction.active;
|
|
2111
2368
|
if (managesTransaction) await client.query('BEGIN');
|
|
2112
2369
|
try {
|
|
2370
|
+
if (input.statementTimeoutMs !== undefined) {
|
|
2371
|
+
await client.query(
|
|
2372
|
+
`SET LOCAL statement_timeout TO ${Math.max(1, Math.floor(input.statementTimeoutMs))}`,
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2113
2375
|
const result = await operation(client);
|
|
2114
2376
|
if (managesTransaction) await client.query('COMMIT');
|
|
2115
2377
|
return result;
|
|
@@ -2122,6 +2384,12 @@ async function withRuntimeSheetQueryClient<T>(
|
|
|
2122
2384
|
},
|
|
2123
2385
|
{
|
|
2124
2386
|
cachePool: !context.disablePostgresPoolCache,
|
|
2387
|
+
maxConnectAttempts:
|
|
2388
|
+
input.statementTimeoutMs === undefined ? undefined : 1,
|
|
2389
|
+
operationTimeoutMs:
|
|
2390
|
+
input.statementTimeoutMs === undefined
|
|
2391
|
+
? undefined
|
|
2392
|
+
: RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS,
|
|
2125
2393
|
signal: context.abortSignal,
|
|
2126
2394
|
},
|
|
2127
2395
|
);
|
|
@@ -6695,6 +6963,8 @@ export type RuntimeMapRowsWriteResult = {
|
|
|
6695
6963
|
fencedKeys: string[];
|
|
6696
6964
|
staleDropped?: number;
|
|
6697
6965
|
staleDroppedKeys?: string[];
|
|
6966
|
+
/** Synthetic Preview-only proof that the gateway applied the page-tail hold. */
|
|
6967
|
+
runtimeTestPageTailHoldMs?: number;
|
|
6698
6968
|
};
|
|
6699
6969
|
|
|
6700
6970
|
type RuntimeMapRowsMutationResult = {
|
|
@@ -7802,6 +8072,9 @@ export async function completeRuntimeMapRows(
|
|
|
7802
8072
|
}),
|
|
7803
8073
|
// Preserve the gateway's compatibility classification during rollout.
|
|
7804
8074
|
fencedKeys: result.fencedKeys,
|
|
8075
|
+
...(typeof result.runtimeTestPageTailHoldMs === 'number'
|
|
8076
|
+
? { runtimeTestPageTailHoldMs: result.runtimeTestPageTailHoldMs }
|
|
8077
|
+
: {}),
|
|
7805
8078
|
};
|
|
7806
8079
|
}
|
|
7807
8080
|
const sheetContract = augmentSheetContractWithDatasetFields({
|
|
@@ -7897,6 +8170,7 @@ export async function completeRuntimeMapRows(
|
|
|
7897
8170
|
tableNamespace: input.tableNamespace,
|
|
7898
8171
|
sheetContract,
|
|
7899
8172
|
transactional: needsTransaction,
|
|
8173
|
+
statementTimeoutMs: RUNTIME_SHEET_COMPLETION_STATEMENT_TIMEOUT_MS,
|
|
7900
8174
|
},
|
|
7901
8175
|
async (client) => {
|
|
7902
8176
|
const completed =
|
|
@@ -23,6 +23,8 @@ export const RUNTIME_CAPACITY_POLICY = {
|
|
|
23
23
|
},
|
|
24
24
|
receiptGateway: {
|
|
25
25
|
admissionTimeoutMs: 10_000,
|
|
26
|
+
/** End identical receipt-row work before the caller can overlap a retry. */
|
|
27
|
+
claimOperationTimeoutMs: 20_000,
|
|
26
28
|
requestTimeoutMs: 30_000,
|
|
27
29
|
},
|
|
28
30
|
} as const;
|
|
@@ -103,6 +105,14 @@ export function assertRuntimeCapacityPolicy(): void {
|
|
|
103
105
|
'Receipt gateway request timeout must exceed its admission timeout.',
|
|
104
106
|
);
|
|
105
107
|
}
|
|
108
|
+
if (
|
|
109
|
+
RUNTIME_CAPACITY_POLICY.receiptGateway.claimOperationTimeoutMs + 5_000 >
|
|
110
|
+
RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs
|
|
111
|
+
) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'Receipt claim operations must leave at least five seconds for cancellation and response delivery.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
106
116
|
if (
|
|
107
117
|
RUNTIME_CAPACITY_POLICY.absurd.perOrgConcurrency >
|
|
108
118
|
ABSURD_GLOBAL_RUN_CONCURRENCY
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const PLAY_RUNTIME_CONTRACT =
|
|
1
|
+
export const PLAY_RUNTIME_CONTRACT = 5;
|
|
2
2
|
export const PLAY_RUNTIME_CONTRACT_MIN_SUPPORTED = 1;
|
|
3
3
|
export const PLAY_RUNTIME_CONTRACT_HEADER = 'x-deepline-runtime-contract';
|
|
4
4
|
/** Shared runner/gateway envelope limit for unacknowledged progress recovery. */
|
|
@@ -27,6 +27,13 @@ export function resolvePlayRuntimeTransportAttemptId(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export const RUNTIME_CONTRACT_CHANGELOG = [
|
|
30
|
+
{
|
|
31
|
+
contract: 5,
|
|
32
|
+
changed:
|
|
33
|
+
'Runtime Sheet completion and lazy launch lookup use bounded, cancellation-aware gateway operations.',
|
|
34
|
+
compatOwner: 'runtime',
|
|
35
|
+
compatRemoval: 'contract 4 runtime lane drain',
|
|
36
|
+
},
|
|
30
37
|
{
|
|
31
38
|
contract: 4,
|
|
32
39
|
changed:
|