bitfab 0.61.3 → 0.61.4
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/{chunk-6IMUONDN.js → chunk-54EGXUZ3.js} +27 -2
- package/dist/chunk-54EGXUZ3.js.map +1 -0
- package/dist/{chunk-IZUZ5NBB.js → chunk-PSAEQ4OW.js} +176 -10
- package/dist/chunk-PSAEQ4OW.js.map +1 -0
- package/dist/{chunk-HVEZHLVQ.js → chunk-VWRCLZCA.js} +26 -2
- package/dist/{chunk-HVEZHLVQ.js.map → chunk-VWRCLZCA.js.map} +1 -1
- package/dist/{chunk-WOHIAKZF.js → chunk-WPYBJGTG.js} +21 -3
- package/dist/chunk-WPYBJGTG.js.map +1 -0
- package/dist/{chunk-PAUXHTNF.js → chunk-XGRQMQ2R.js} +6 -6
- package/dist/{chunk-PAUXHTNF.js.map → chunk-XGRQMQ2R.js.map} +1 -1
- package/dist/{http-BYICBOWP.js → http-C2TCBBQV.js} +2 -2
- package/dist/{http-3IYU5Z54.js → http-ONLJPP6W.js} +2 -2
- package/dist/index.cjs +214 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +3 -3
- package/dist/node.cjs +214 -9
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{replay-SXHX5BBU.js → replay-RM24QKEA.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-6IMUONDN.js.map +0 -1
- package/dist/chunk-IZUZ5NBB.js.map +0 -1
- package/dist/chunk-WOHIAKZF.js.map +0 -1
- /package/dist/{http-3IYU5Z54.js.map → http-C2TCBBQV.js.map} +0 -0
- /package/dist/{http-BYICBOWP.js.map → http-ONLJPP6W.js.map} +0 -0
- /package/dist/{replay-SXHX5BBU.js.map → replay-RM24QKEA.js.map} +0 -0
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
flushTraces,
|
|
5
5
|
gitRunner,
|
|
6
6
|
replayContextReady,
|
|
7
|
-
runWithReplayContext
|
|
8
|
-
|
|
7
|
+
runWithReplayContext,
|
|
8
|
+
unrefTimer
|
|
9
|
+
} from "./chunk-54EGXUZ3.js";
|
|
9
10
|
import {
|
|
10
11
|
runWithCaptureSuppressed,
|
|
11
12
|
warnOnce
|
|
@@ -437,6 +438,101 @@ function fallbackUuidV4() {
|
|
|
437
438
|
});
|
|
438
439
|
}
|
|
439
440
|
|
|
441
|
+
// src/replayHeartbeat.ts
|
|
442
|
+
var REPLAY_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
443
|
+
function startReplayHeartbeat(send) {
|
|
444
|
+
let stopped = false;
|
|
445
|
+
let inFlight;
|
|
446
|
+
const timer = setInterval(() => {
|
|
447
|
+
if (stopped || inFlight !== void 0) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
inFlight = send().catch(() => void 0).finally(() => {
|
|
451
|
+
inFlight = void 0;
|
|
452
|
+
});
|
|
453
|
+
}, REPLAY_HEARTBEAT_INTERVAL_MS);
|
|
454
|
+
unrefTimer(timer);
|
|
455
|
+
return {
|
|
456
|
+
stop: async () => {
|
|
457
|
+
stopped = true;
|
|
458
|
+
clearInterval(timer);
|
|
459
|
+
await inFlight;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/replayInterrupt.ts
|
|
465
|
+
var REPLAY_INTERRUPT_SIGNALS = [
|
|
466
|
+
"SIGINT",
|
|
467
|
+
"SIGTERM"
|
|
468
|
+
];
|
|
469
|
+
function signalCapableProcess() {
|
|
470
|
+
if (typeof process === "undefined" || typeof process.on !== "function") {
|
|
471
|
+
return void 0;
|
|
472
|
+
}
|
|
473
|
+
return process;
|
|
474
|
+
}
|
|
475
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
476
|
+
if (markedInterrupted) {
|
|
477
|
+
return `[replay] Experiment ${experimentId} interrupted.`;
|
|
478
|
+
}
|
|
479
|
+
return `[replay] Experiment ${experimentId} stopped, but Bitfab could not mark it interrupted. It will be marked interrupted within 15 minutes. A resume in the next two minutes may be refused as still running.`;
|
|
480
|
+
}
|
|
481
|
+
function reportInterrupted(experimentId, markedInterrupted) {
|
|
482
|
+
try {
|
|
483
|
+
console.error(describeReplayInterrupt(experimentId, markedInterrupted));
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function installReplayInterruptHandlers(target) {
|
|
488
|
+
const host = signalCapableProcess();
|
|
489
|
+
if (host === void 0) {
|
|
490
|
+
return { remove: () => {
|
|
491
|
+
}, interrupted: () => false };
|
|
492
|
+
}
|
|
493
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
494
|
+
const removeHandlers = () => {
|
|
495
|
+
for (const [signal, handler] of handlers) {
|
|
496
|
+
host.removeListener(signal, handler);
|
|
497
|
+
}
|
|
498
|
+
handlers.clear();
|
|
499
|
+
};
|
|
500
|
+
let interrupting = false;
|
|
501
|
+
const handleSignal = async (signal) => {
|
|
502
|
+
const hostHandlesSignal = host.listenerCount(signal) > 1;
|
|
503
|
+
const markedInterrupted = await target.interrupt();
|
|
504
|
+
removeHandlers();
|
|
505
|
+
if (target.onInterrupt !== void 0) {
|
|
506
|
+
try {
|
|
507
|
+
target.onInterrupt({
|
|
508
|
+
experimentId: target.experimentId,
|
|
509
|
+
experimentUrl: target.experimentUrl,
|
|
510
|
+
signal,
|
|
511
|
+
markedInterrupted
|
|
512
|
+
});
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
reportInterrupted(target.experimentId, markedInterrupted);
|
|
518
|
+
if (!hostHandlesSignal) {
|
|
519
|
+
host.kill(host.pid, signal);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
for (const signal of REPLAY_INTERRUPT_SIGNALS) {
|
|
523
|
+
const handler = () => {
|
|
524
|
+
if (interrupting) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
interrupting = true;
|
|
528
|
+
void handleSignal(signal);
|
|
529
|
+
};
|
|
530
|
+
handlers.set(signal, handler);
|
|
531
|
+
host.on(signal, handler);
|
|
532
|
+
}
|
|
533
|
+
return { remove: removeHandlers, interrupted: () => interrupting };
|
|
534
|
+
}
|
|
535
|
+
|
|
440
536
|
// src/selectiveReplay.ts
|
|
441
537
|
function replayNodeKey(node) {
|
|
442
538
|
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
@@ -1357,7 +1453,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1357
1453
|
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
1358
1454
|
let session;
|
|
1359
1455
|
if (options?.resume === void 0) {
|
|
1360
|
-
const
|
|
1456
|
+
const started = await httpClient.startReplay(
|
|
1361
1457
|
traceFunctionKey,
|
|
1362
1458
|
// limit is meaningless with explicit traceIds (the ID list determines
|
|
1363
1459
|
// the count), so it's omitted from the request entirely.
|
|
@@ -1380,7 +1476,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1380
1476
|
options?.judgeAssertions,
|
|
1381
1477
|
options?.experimentalSelectiveReplay
|
|
1382
1478
|
);
|
|
1383
|
-
if (
|
|
1479
|
+
if (started.items.length === 0) {
|
|
1384
1480
|
try {
|
|
1385
1481
|
console.warn(
|
|
1386
1482
|
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
@@ -1389,10 +1485,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1389
1485
|
}
|
|
1390
1486
|
}
|
|
1391
1487
|
session = {
|
|
1392
|
-
experimentId:
|
|
1393
|
-
experimentUrl:
|
|
1488
|
+
experimentId: started.experimentId,
|
|
1489
|
+
experimentUrl: started.experimentUrl,
|
|
1394
1490
|
attempts: requestedAttempts,
|
|
1395
|
-
work: expandReplayWork(
|
|
1491
|
+
work: expandReplayWork(started.items, requestedAttempts),
|
|
1396
1492
|
finished: [],
|
|
1397
1493
|
includeDbBranchLease,
|
|
1398
1494
|
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
@@ -1407,9 +1503,68 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1407
1503
|
options.dbBranch
|
|
1408
1504
|
);
|
|
1409
1505
|
}
|
|
1410
|
-
const { experimentId
|
|
1506
|
+
const { experimentId } = session;
|
|
1507
|
+
const fullExperimentUrl = `${serviceUrl}${session.experimentUrl}`;
|
|
1508
|
+
const heartbeat = startReplayHeartbeat(
|
|
1509
|
+
() => httpClient.heartbeatReplay(experimentId)
|
|
1510
|
+
);
|
|
1511
|
+
const interruptReplay = async () => {
|
|
1512
|
+
await heartbeat.stop();
|
|
1513
|
+
return httpClient.interruptReplay(experimentId);
|
|
1514
|
+
};
|
|
1515
|
+
const interruptHandlers = installReplayInterruptHandlers({
|
|
1516
|
+
experimentId,
|
|
1517
|
+
experimentUrl: fullExperimentUrl,
|
|
1518
|
+
interrupt: interruptReplay,
|
|
1519
|
+
onInterrupt: options?.onInterrupt
|
|
1520
|
+
});
|
|
1521
|
+
let completeReturned = false;
|
|
1522
|
+
const markCompleteReturned = () => {
|
|
1523
|
+
completeReturned = true;
|
|
1524
|
+
void heartbeat.stop();
|
|
1525
|
+
};
|
|
1526
|
+
try {
|
|
1527
|
+
return await runReplaySession(
|
|
1528
|
+
httpClient,
|
|
1529
|
+
fn,
|
|
1530
|
+
session,
|
|
1531
|
+
fullExperimentUrl,
|
|
1532
|
+
maxConcurrency,
|
|
1533
|
+
interruptHandlers.remove,
|
|
1534
|
+
markCompleteReturned,
|
|
1535
|
+
interruptHandlers.interrupted,
|
|
1536
|
+
options,
|
|
1537
|
+
registeredOverrides
|
|
1538
|
+
);
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (!completeReturned && !interruptHandlers.interrupted()) {
|
|
1541
|
+
await interruptReplay();
|
|
1542
|
+
}
|
|
1543
|
+
throw error;
|
|
1544
|
+
} finally {
|
|
1545
|
+
interruptHandlers.remove();
|
|
1546
|
+
await heartbeat.stop();
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
function throwIfInterrupted(interrupted, items, experimentId, experimentUrl) {
|
|
1550
|
+
if (!interrupted()) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
const cause = new BitfabError(
|
|
1554
|
+
`Experiment ${experimentId} was interrupted, so it was not completed or graded. Resume it by passing { resume: "${experimentId}" } to replay, or with bitfab-replay --resume ${experimentId}.`,
|
|
1555
|
+
experimentUrl
|
|
1556
|
+
);
|
|
1557
|
+
throw new ReplayError(
|
|
1558
|
+
cause.message,
|
|
1559
|
+
items,
|
|
1560
|
+
experimentId,
|
|
1561
|
+
experimentUrl,
|
|
1562
|
+
cause
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
async function runReplaySession(httpClient, fn, session, fullExperimentUrl, maxConcurrency, stopInterruptingOnSignal, markCompleteReturned, interrupted, options, registeredOverrides) {
|
|
1566
|
+
const { experimentId, attempts } = session;
|
|
1411
1567
|
const mockStrategy = options?.mock ?? "marked";
|
|
1412
|
-
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
1413
1568
|
try {
|
|
1414
1569
|
options?.onExperimentStart?.({
|
|
1415
1570
|
experimentId,
|
|
@@ -1559,7 +1714,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1559
1714
|
);
|
|
1560
1715
|
const resultItems = [...carriedOverItems, ...ranItems];
|
|
1561
1716
|
if (options?.dryRun === true) {
|
|
1717
|
+
throwIfInterrupted(
|
|
1718
|
+
interrupted,
|
|
1719
|
+
resultItems,
|
|
1720
|
+
experimentId,
|
|
1721
|
+
fullExperimentUrl
|
|
1722
|
+
);
|
|
1723
|
+
stopInterruptingOnSignal();
|
|
1562
1724
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
1725
|
+
markCompleteReturned();
|
|
1563
1726
|
const dryResult = {
|
|
1564
1727
|
items: resultItems,
|
|
1565
1728
|
experimentId,
|
|
@@ -1584,12 +1747,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
1584
1747
|
ranItems[index].traceId = readBack;
|
|
1585
1748
|
}
|
|
1586
1749
|
}
|
|
1750
|
+
throwIfInterrupted(interrupted, resultItems, experimentId, fullExperimentUrl);
|
|
1751
|
+
stopInterruptingOnSignal();
|
|
1587
1752
|
const completeResult = await preserveReplayFailure(
|
|
1588
1753
|
() => httpClient.completeReplay(experimentId),
|
|
1589
1754
|
resultItems,
|
|
1590
1755
|
experimentId,
|
|
1591
1756
|
fullExperimentUrl
|
|
1592
1757
|
);
|
|
1758
|
+
markCompleteReturned();
|
|
1593
1759
|
const serverTraceIds = completeResult.traceIds;
|
|
1594
1760
|
const replayTokens = completeResult.tokens;
|
|
1595
1761
|
if (serverTraceIds !== void 0) {
|
|
@@ -1702,4 +1868,4 @@ export {
|
|
|
1702
1868
|
sleepForReplayPersistence,
|
|
1703
1869
|
replay
|
|
1704
1870
|
};
|
|
1705
|
-
//# sourceMappingURL=chunk-
|
|
1871
|
+
//# sourceMappingURL=chunk-PSAEQ4OW.js.map
|