@loadstrike/loadstrike-sdk 1.0.30401 → 1.0.31001

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.
@@ -10,6 +10,8 @@ import { buildDotnetCsvReport, buildDotnetHtmlReport, buildDotnetMarkdownReport,
10
10
  import { PortalReportingSink, cloneReportingSinkForRun } from "./sinks.js";
11
11
  import { LoadEngineV2ExecutionBudget, buildLoadEngineV2TrafficMixSeedId, LoadStrikeHistogramV1, parseLoadEngineV2HistogramArtifact, serializeLoadEngineV2HistogramArtifact, classifyLoadEngineV2Arrival, loadEngineV2FixedArrivalCount, loadEngineV2FixedDeadlineNs, loadEngineV2LatenessToleranceNs, loadEngineV2TrafficMixLaneUnitCount, loadEngineV2TrafficMixOwnedUnits, planRampingInjectionDeadlines, planRampingConstantDeadlines, planRandomInjectionDeadlines, fnv1a32 } from "./load-engine-v2.js";
12
12
  import { DEFAULT_ITERATION_OBSERVATION_SETTINGS, IterationObservationReporter, createIterationObservation, createIterationStepObservation, utcNowNs, validateIterationObservationSettings } from "./iteration-observations.js";
13
+ import { logIterationObservationFailure, logIterationObservationRecovery } from "./iteration-observation-diagnostics.js";
14
+ import { normalizeSinkRetryBackoffMs, normalizeSinkRetryCount, sinkRetryDelayMs, waitForSinkRetryDelay } from "./sink-retry-policy.js";
13
15
  const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
14
16
  const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
15
17
  export const LoadStrikeNodeType = {
@@ -3427,8 +3429,8 @@ export class LoadStrikeRunner {
3427
3429
  }));
3428
3430
  const sinkErrors = [];
3429
3431
  const policyErrors = [];
3430
- const sinkRetryCount = Math.max(this.options.sinkRetryCount ?? 2, 0);
3431
- const sinkRetryBackoffMs = Math.max(this.options.sinkRetryBackoffMs ?? 25, 0);
3432
+ const sinkRetryCount = normalizeSinkRetryCount(this.options.sinkRetryCount);
3433
+ const sinkRetryBackoffMs = normalizeSinkRetryBackoffMs(this.options.sinkRetryBackoffMs);
3432
3434
  const policies = this.options.runtimePolicies ?? [];
3433
3435
  const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
3434
3436
  const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
@@ -3522,14 +3524,14 @@ export class LoadStrikeRunner {
3522
3524
  await init(baseContext, this.options.infraConfig ?? {});
3523
3525
  }
3524
3526
  }
3525
- await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3527
+ await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3526
3528
  for (const plugin of plugins) {
3527
3529
  const start = resolveWorkerPluginStart(plugin);
3528
3530
  if (start) {
3529
3531
  await start(sessionInfo);
3530
3532
  }
3531
3533
  }
3532
- await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3534
+ await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3533
3535
  const iterationObservationRunId = String(this.internalOptions.iterationObservationRunId
3534
3536
  ?? sessionInfo.portalReportingRunId
3535
3537
  ?? sessionInfo.PortalReportingRunId
@@ -3560,7 +3562,10 @@ export class LoadStrikeRunner {
3560
3562
  expectedResultOwnerCount64: iterationObservationExpectedResultOwnerCount64,
3561
3563
  processGroup: iterationObservationProcessGroup,
3562
3564
  settings: resolveIterationObservationSettings(this.options),
3563
- sinks: reporterIterationObservationSinks
3565
+ sinks: reporterIterationObservationSinks,
3566
+ sinkRetryCount,
3567
+ sinkRetryBackoffMs,
3568
+ logger: runLogger
3564
3569
  });
3565
3570
  const emitRealtimeSnapshot = async () => {
3566
3571
  if (realtimeInFlight) {
@@ -3572,7 +3577,7 @@ export class LoadStrikeRunner {
3572
3577
  .map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? Math.max(Date.now() - started.getTime(), 0)))
3573
3578
  .sort((left, right) => left.sortIndex - right.sortIndex);
3574
3579
  const metricsSnapshot = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
3575
- await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3580
+ await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3576
3581
  if (toBoolean(this.options.displayConsoleMetrics, true)) {
3577
3582
  const requestCount = snapshot.reduce((sum, value) => sum + value.allRequestCount, 0);
3578
3583
  const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
@@ -3763,8 +3768,8 @@ export class LoadStrikeRunner {
3763
3768
  finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
3764
3769
  finalizedResult.reportFiles = this.writeReports(finalizedResult);
3765
3770
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
3766
- await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3767
- await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3771
+ await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3772
+ await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3768
3773
  sinksStopped = true;
3769
3774
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
3770
3775
  finalizedResult.sinkErrors = sinkErrors
@@ -3777,7 +3782,7 @@ export class LoadStrikeRunner {
3777
3782
  await iterationObservationReporter.sealAndDrain().catch(() => { });
3778
3783
  }
3779
3784
  if (!sinksStopped) {
3780
- await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
3785
+ await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
3781
3786
  }
3782
3787
  if (!pluginsStopped) {
3783
3788
  await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
@@ -4085,9 +4090,9 @@ export class LoadStrikeRunner {
4085
4090
  }
4086
4091
  return filtered;
4087
4092
  }
4088
- async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
4093
+ async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
4089
4094
  for (const state of sinkStates) {
4090
- await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4095
+ await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
4091
4096
  const init = resolveSinkInit(state.sink);
4092
4097
  if (init) {
4093
4098
  await init(context, infraConfig);
@@ -4095,9 +4100,9 @@ export class LoadStrikeRunner {
4095
4100
  });
4096
4101
  }
4097
4102
  }
4098
- async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors) {
4103
+ async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
4099
4104
  for (const state of sinkStates) {
4100
- await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4105
+ await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
4101
4106
  const start = resolveSinkStart(state.sink);
4102
4107
  if (start) {
4103
4108
  await start(session);
@@ -4105,9 +4110,9 @@ export class LoadStrikeRunner {
4105
4110
  });
4106
4111
  }
4107
4112
  }
4108
- async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors) {
4113
+ async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
4109
4114
  for (const state of sinkStates) {
4110
- await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
4115
+ await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
4111
4116
  const saveRealtimeStats = resolveSinkSaveRealtimeStats(state.sink);
4112
4117
  if (saveRealtimeStats) {
4113
4118
  await saveRealtimeStats(scenarioStats);
@@ -4119,10 +4124,9 @@ export class LoadStrikeRunner {
4119
4124
  });
4120
4125
  }
4121
4126
  }
4122
- async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
4123
- const shutdownRetryCount = 0;
4127
+ async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
4124
4128
  for (const state of sinkStates) {
4125
- await this.invokeSinkAction(state, "stop", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
4129
+ await this.invokeSinkAction(state, "stop", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
4126
4130
  const stop = resolveSinkStop(state.sink);
4127
4131
  if (stop) {
4128
4132
  await stop();
@@ -4130,15 +4134,15 @@ export class LoadStrikeRunner {
4130
4134
  });
4131
4135
  const dispose = resolveSinkDispose(state.sink);
4132
4136
  if (dispose) {
4133
- await this.invokeSinkAction(state, "dispose", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
4137
+ await this.invokeSinkAction(state, "dispose", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
4134
4138
  await dispose();
4135
4139
  });
4136
4140
  }
4137
4141
  }
4138
4142
  }
4139
- async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
4143
+ async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
4140
4144
  for (const state of sinkStates) {
4141
- await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
4145
+ await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, false, async () => {
4142
4146
  const saveRunResult = resolveSinkSaveRunResult(state.sink);
4143
4147
  if (saveRunResult) {
4144
4148
  await saveRunResult(result);
@@ -4146,23 +4150,49 @@ export class LoadStrikeRunner {
4146
4150
  });
4147
4151
  }
4148
4152
  }
4149
- async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, ignoreDisabled, disableOnFailure, action) {
4153
+ async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, runId, logger, ignoreDisabled, disableOnFailure, action) {
4150
4154
  if (state.disabled && !ignoreDisabled) {
4151
4155
  return;
4152
4156
  }
4153
- let attempts = 0;
4154
- while (attempts <= retryCount) {
4155
- attempts += 1;
4157
+ const maximumAttempts = normalizeSinkRetryCount(retryCount) + 1;
4158
+ const backoffMs = normalizeSinkRetryBackoffMs(retryBackoffMs);
4159
+ for (let attempts = 1; attempts <= maximumAttempts; attempts += 1) {
4156
4160
  try {
4157
4161
  await action();
4162
+ if (attempts > 1) {
4163
+ logIterationObservationRecovery(logger, {
4164
+ sinkName: state.name,
4165
+ operation: "reporting-sink-action",
4166
+ phase,
4167
+ runId,
4168
+ resultOwnerId: "",
4169
+ observationCount: 0,
4170
+ attempt: attempts,
4171
+ maximumAttempts,
4172
+ nextDelayMs: 0
4173
+ });
4174
+ }
4158
4175
  return;
4159
4176
  }
4160
4177
  catch (error) {
4161
- if (attempts > retryCount) {
4178
+ const exhausted = attempts >= maximumAttempts;
4179
+ const nextDelayMs = exhausted ? 0 : sinkRetryDelayMs(backoffMs, attempts);
4180
+ logIterationObservationFailure(logger, exhausted ? "error" : "warn", {
4181
+ sinkName: state.name,
4182
+ operation: "reporting-sink-action",
4183
+ phase,
4184
+ runId,
4185
+ resultOwnerId: "",
4186
+ observationCount: 0,
4187
+ attempt: attempts,
4188
+ maximumAttempts,
4189
+ nextDelayMs
4190
+ }, error);
4191
+ if (exhausted) {
4162
4192
  sinkErrors.push({
4163
4193
  sinkName: state.name,
4164
4194
  phase,
4165
- message: String(error ?? "sink action failed"),
4195
+ message: "The reporting sink action failed after retries.",
4166
4196
  attempts
4167
4197
  });
4168
4198
  if (disableOnFailure) {
@@ -4170,9 +4200,7 @@ export class LoadStrikeRunner {
4170
4200
  }
4171
4201
  return;
4172
4202
  }
4173
- if (retryBackoffMs > 0) {
4174
- await sleep(retryBackoffMs * attempts);
4175
- }
4203
+ await waitForSinkRetryDelay(nextDelayMs);
4176
4204
  }
4177
4205
  }
4178
4206
  }
@@ -4581,7 +4609,18 @@ async function executeScenarioRuntime(args) {
4581
4609
  attachScenarioContextAliases(context);
4582
4610
  const startedUtcNs = utcNowNs();
4583
4611
  const startedAtNs = process.hrtime.bigint();
4584
- const reply = await executeScenarioInvocation(scenario, context, operation);
4612
+ let policyFailure;
4613
+ let reply;
4614
+ try {
4615
+ reply = await executeScenarioInvocation(scenario, context, operation);
4616
+ }
4617
+ catch (error) {
4618
+ if (!(error instanceof RuntimePolicyCallbackError)) {
4619
+ throw error;
4620
+ }
4621
+ policyFailure = error;
4622
+ reply = LoadStrikeResponse.fail("runtime_policy_error", "", 0);
4623
+ }
4585
4624
  const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
4586
4625
  const completedUtcNs = startedUtcNs + observedLatencyNs;
4587
4626
  const observedLatencyMs = Number(observedLatencyNs) / 1000000;
@@ -4596,7 +4635,8 @@ async function executeScenarioRuntime(args) {
4596
4635
  observedLatencyUs: observedLatencyNs / 1000n,
4597
4636
  reportedLatencyUs: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
4598
4637
  steps: attemptSteps,
4599
- recordedSteps
4638
+ recordedSteps,
4639
+ ...(policyFailure ? { policyFailure } : {})
4600
4640
  };
4601
4641
  };
4602
4642
  const captureAttemptObservation = (operation, globalOrdinal, attemptIndex, isFinalAttempt, attempt, simulationIndex, simulationKind, iterationId, globalSecondaryOrdinal = 0n) => {
@@ -4645,22 +4685,18 @@ async function executeScenarioRuntime(args) {
4645
4685
  const maxAttempts = 1 + (scenario.shouldRestartIterationOnFail() ? restartIterationMaxAttempts : 0);
4646
4686
  while (attempts < maxAttempts && !shouldStopNow()) {
4647
4687
  attempts += 1;
4648
- let attempt;
4649
- try {
4650
- attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
4651
- }
4652
- catch (error) {
4653
- if (error instanceof RuntimePolicyCallbackError) {
4654
- stopScenario = true;
4655
- scenarioAbortController.abort(error);
4656
- }
4657
- throw error;
4658
- }
4659
- const shouldRetry = !attempt.reply.isSuccess
4688
+ const attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
4689
+ const shouldRetry = !attempt.policyFailure
4690
+ && !attempt.reply.isSuccess
4660
4691
  && scenario.shouldRestartIterationOnFail()
4661
4692
  && attempts < maxAttempts
4662
4693
  && !shouldStopNow();
4663
4694
  captureAttemptObservation("Bombing", globalOrdinal, attempts - 1, !shouldRetry, attempt, simulationIndex, simulationKind, iterationId, explicitSecondaryOrdinal);
4695
+ if (attempt.policyFailure) {
4696
+ stopScenario = true;
4697
+ scenarioAbortController.abort(attempt.policyFailure);
4698
+ throw attempt.policyFailure;
4699
+ }
4664
4700
  if (!shouldRetry) {
4665
4701
  for (const step of attempt.recordedSteps) {
4666
4702
  recordStepReply(step.stepName, step.reply, step.observedLatencyMs, step.sortIndex);
@@ -4687,6 +4723,11 @@ async function executeScenarioRuntime(args) {
4687
4723
  const globalOrdinal = nextObservationOrdinal();
4688
4724
  const attempt = await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
4689
4725
  captureAttemptObservation("WarmUp", globalOrdinal, 0, true, attempt, -1, "SingleInvocation");
4726
+ if (attempt.policyFailure) {
4727
+ stopScenario = true;
4728
+ scenarioAbortController.abort(attempt.policyFailure);
4729
+ throw attempt.policyFailure;
4730
+ }
4690
4731
  }
4691
4732
  };
4692
4733
  const executeV2TimedConstant = async (copies, durationNs, ramping, runSimulationInvocation, segment) => {
@@ -5357,10 +5398,17 @@ async function waitForScenarioTasks(tasks, scenarioName, timeoutSeconds, logger,
5357
5398
  throwPolicyFailure(await settled);
5358
5399
  return;
5359
5400
  }
5360
- const completed = await Promise.race([
5361
- settled.then(() => true),
5362
- delayWithAbort(Math.trunc(timeoutSeconds * 1000), signal).then(() => false)
5363
- ]);
5401
+ const timeoutController = new AbortController();
5402
+ let completed;
5403
+ try {
5404
+ completed = await Promise.race([
5405
+ settled.then(() => true),
5406
+ delayWithAbort(Math.trunc(timeoutSeconds * 1000), combineAbortSignals(signal, timeoutController.signal)).then(() => false)
5407
+ ]);
5408
+ }
5409
+ finally {
5410
+ timeoutController.abort();
5411
+ }
5364
5412
  if (!completed) {
5365
5413
  if (signal.reason instanceof RuntimePolicyCallbackError) {
5366
5414
  throw signal.reason;
@@ -9422,11 +9470,23 @@ function readRuntimeTrackingId(payload, selector) {
9422
9470
  return null;
9423
9471
  }
9424
9472
  let current = body;
9425
- for (const segment of selector.slice("json:".length).trim().replace(/^\$\./, "").split(".").filter(Boolean)) {
9473
+ const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
9474
+ let segments;
9475
+ try {
9476
+ segments = runtimeSafeJsonPathSegments(path);
9477
+ }
9478
+ catch {
9479
+ return null;
9480
+ }
9481
+ for (const segment of segments) {
9426
9482
  if (!current || typeof current !== "object" || Array.isArray(current)) {
9427
9483
  return null;
9428
9484
  }
9429
- current = current[segment];
9485
+ const record = current;
9486
+ if (!Object.prototype.hasOwnProperty.call(record, segment)) {
9487
+ return null;
9488
+ }
9489
+ current = runtimeReadOwnJsonProperty(record, segment);
9430
9490
  }
9431
9491
  return current == null ? null : String(current);
9432
9492
  }
@@ -9450,23 +9510,57 @@ function runtimeParseBodyAsObject(body) {
9450
9510
  }
9451
9511
  }
9452
9512
  function setRuntimeJsonPathValue(body, path, value) {
9453
- const target = body && typeof body === "object" && !Array.isArray(body)
9454
- ? { ...body }
9455
- : {};
9456
- const segments = path.split(".").filter(Boolean);
9513
+ const target = runtimeCloneJsonRecord(body);
9514
+ const segments = runtimeSafeJsonPathSegments(path);
9457
9515
  if (!segments.length) {
9458
9516
  return target;
9459
9517
  }
9460
9518
  let current = target;
9461
9519
  for (let i = 0; i < segments.length - 1; i += 1) {
9462
9520
  const segment = segments[i];
9463
- const next = current[segment];
9521
+ const next = runtimeReadOwnJsonProperty(current, segment);
9522
+ let child;
9464
9523
  if (!next || typeof next !== "object" || Array.isArray(next)) {
9465
- current[segment] = {};
9524
+ child = {};
9525
+ }
9526
+ else {
9527
+ child = runtimeCloneJsonRecord(next);
9466
9528
  }
9467
- current = current[segment];
9529
+ runtimeDefineJsonProperty(current, segment, child);
9530
+ current = child;
9531
+ }
9532
+ runtimeDefineJsonProperty(current, segments[segments.length - 1], value);
9533
+ return target;
9534
+ }
9535
+ const FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
9536
+ function runtimeSafeJsonPathSegments(path) {
9537
+ const segments = path.split(".").filter(Boolean);
9538
+ const forbidden = segments.find((segment) => FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS.has(segment));
9539
+ if (forbidden) {
9540
+ throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
9541
+ }
9542
+ return segments;
9543
+ }
9544
+ function runtimeDefineJsonProperty(target, key, value) {
9545
+ Object.defineProperty(target, key, {
9546
+ configurable: true,
9547
+ enumerable: true,
9548
+ value,
9549
+ writable: true
9550
+ });
9551
+ }
9552
+ function runtimeReadOwnJsonProperty(target, key) {
9553
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
9554
+ return descriptor && "value" in descriptor ? descriptor.value : undefined;
9555
+ }
9556
+ function runtimeCloneJsonRecord(value) {
9557
+ const target = {};
9558
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9559
+ return target;
9560
+ }
9561
+ for (const [key, entry] of Object.entries(value)) {
9562
+ runtimeDefineJsonProperty(target, key, entry);
9468
9563
  }
9469
- current[segments[segments.length - 1]] = value;
9470
9564
  return target;
9471
9565
  }
9472
9566
  function asTrackingRecord(value) {
@@ -9476,8 +9570,8 @@ function asTrackingRecord(value) {
9476
9570
  }
9477
9571
  function pickTrackingValue(source, ...keys) {
9478
9572
  for (const key of keys) {
9479
- if (key in source) {
9480
- return source[key];
9573
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
9574
+ return runtimeReadOwnJsonProperty(source, key);
9481
9575
  }
9482
9576
  }
9483
9577
  return undefined;
@@ -9731,26 +9825,10 @@ function createDefaultLogger(logFilePath) {
9731
9825
  function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
9732
9826
  const threshold = logLevelOrder(minimumLogLevel);
9733
9827
  return {
9734
- debug: (message) => {
9735
- if (threshold <= 0) {
9736
- baseLogger.debug(message);
9737
- }
9738
- },
9739
- info: (message) => {
9740
- if (threshold <= 1) {
9741
- baseLogger.info(message);
9742
- }
9743
- },
9744
- warn: (message) => {
9745
- if (threshold <= 2) {
9746
- baseLogger.warn(message);
9747
- }
9748
- },
9749
- error: (message) => {
9750
- if (threshold <= 3) {
9751
- baseLogger.error(message);
9752
- }
9753
- }
9828
+ debug: (message) => threshold <= 0 ? baseLogger.debug(message) : undefined,
9829
+ info: (message) => threshold <= 1 ? baseLogger.info(message) : undefined,
9830
+ warn: (message) => threshold <= 2 ? baseLogger.warn(message) : undefined,
9831
+ error: (message) => threshold <= 3 ? baseLogger.error(message) : undefined
9754
9832
  };
9755
9833
  }
9756
9834
  function formatDefaultLoggerLine(level, message) {
@@ -11264,6 +11342,7 @@ export const __loadstrikeTestExports = {
11264
11342
  parseStrictBooleanToken,
11265
11343
  percentile,
11266
11344
  pickOptionalTrackingSelectorString,
11345
+ pickTrackingValue,
11267
11346
  pickTrackingNumber,
11268
11347
  produceOrConsumeTrackingPayload,
11269
11348
  readConfiguredSinkName,
@@ -0,0 +1,44 @@
1
+ export const DEFAULT_SINK_RETRY_COUNT = 3;
2
+ export const DEFAULT_SINK_RETRY_BACKOFF_MS = 250;
3
+ export const MAXIMUM_SINK_RETRY_COUNT = 100;
4
+ export const MAXIMUM_TIMER_DELAY_MS = 2147483647;
5
+ export function normalizeSinkRetryCount(value) {
6
+ const resolved = value === undefined ? DEFAULT_SINK_RETRY_COUNT : value;
7
+ if (!Number.isFinite(resolved) || resolved <= 0) {
8
+ return 0;
9
+ }
10
+ return Math.min(Math.trunc(resolved), MAXIMUM_SINK_RETRY_COUNT);
11
+ }
12
+ export function normalizeSinkRetryBackoffMs(value) {
13
+ const resolved = value === undefined ? DEFAULT_SINK_RETRY_BACKOFF_MS : value;
14
+ if (!Number.isFinite(resolved) || resolved <= 0) {
15
+ return 0;
16
+ }
17
+ return Math.min(Math.trunc(resolved), MAXIMUM_TIMER_DELAY_MS);
18
+ }
19
+ export function sinkRetryDelayMs(baseDelayMs, retryNumber) {
20
+ if (baseDelayMs <= 0) {
21
+ return 0;
22
+ }
23
+ let delayMs = Math.min(Math.trunc(baseDelayMs), MAXIMUM_TIMER_DELAY_MS);
24
+ const doublings = Math.max(Math.trunc(retryNumber) - 1, 0);
25
+ for (let index = 0; index < doublings; index += 1) {
26
+ if (delayMs >= Math.ceil(MAXIMUM_TIMER_DELAY_MS / 2)) {
27
+ return MAXIMUM_TIMER_DELAY_MS;
28
+ }
29
+ delayMs *= 2;
30
+ }
31
+ return delayMs;
32
+ }
33
+ export async function waitForSinkRetryDelay(delayMs) {
34
+ if (delayMs <= 0) {
35
+ await cooperativeSinkRetryYield();
36
+ return;
37
+ }
38
+ await new Promise((resolve) => {
39
+ setTimeout(resolve, Math.min(delayMs, MAXIMUM_TIMER_DELAY_MS));
40
+ });
41
+ }
42
+ export async function cooperativeSinkRetryYield() {
43
+ await new Promise((resolve) => setImmediate(resolve));
44
+ }