@mark1russell7/service 0.5.4 → 0.5.5

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/build/index.js CHANGED
@@ -3,9 +3,9 @@ import { detectionTime, heartbeat } from "./heartbeat.js";
3
3
  import { a as recoverSpan, i as heartbeatDetection, n as asWindow, r as failSpan, t as asDebounce } from "./timing-DGRi3XCI.js";
4
4
  import { n as operationProblems, t as mayReissue } from "./operation-DL5RxFds.js";
5
5
  import { a as limiterProblems, i as limitRule, n as freeAt, o as longestRetryAfter, r as idleSpan, s as retryAfterSeconds, t as decide } from "./policy-CgS4y94U.js";
6
+ import { a as lateHolds, n as countOf, r as millisecondsOf, t as boundedMillisecondsOf } from "./knobs-BZAsgb5H.js";
6
7
  import { backoffBand, backoffDelay, backoffProblems } from "./backoff.js";
7
8
  import { n as memoryStore, t as keyedLimiter } from "./limiter-wv0uXDk9.js";
8
- import { n as countOf, r as millisecondsOf, t as boundedMillisecondsOf } from "./knobs-BxG-c7Zb.js";
9
9
  import { convertExact, convertible, quantity, rationals, valueIn } from "@mark1russell7/quantity";
10
10
  import { replay } from "@mark1russell7/algebra/replay";
11
11
  //#region src/description.ts
@@ -1150,8 +1150,11 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1150
1150
  if (found.as === void 0) triggered("ipc-message");
1151
1151
  else triggered(found.as, `${found.as} (a message of the IPC channel)`);
1152
1152
  };
1153
- /** This function does the work of one phase, until its cap. It gives true when the work ended in time. */
1154
- const runPhase = async (phase, hold, work) => {
1153
+ /**
1154
+ * This function does the work of one phase, until its cap. It tells if the work ended in time. When
1155
+ * work is left at the cap, `excuse` gives the cause of a cap that is not a failure, or null.
1156
+ */
1157
+ const runPhase = async (phase, hold, work, excuse = () => null) => {
1155
1158
  const cap = valueIn(phase.cap, "ms");
1156
1159
  const deadline = clock.now() + cap;
1157
1160
  const capped = new AbortController();
@@ -1175,9 +1178,13 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1175
1178
  cancelCap = clock.after(cap, () => resolve("cap"));
1176
1179
  });
1177
1180
  if (await Promise.race([all.then(() => "work"), end]) === "work" && hold) await end;
1181
+ const excused = pending > 0 ? excuse(pending) : null;
1178
1182
  cancelCap();
1179
1183
  capped.abort(/* @__PURE__ */ new Error(`the phase ${phase.name} reached its cap`));
1180
- return pending === 0;
1184
+ return {
1185
+ inTime: pending === 0,
1186
+ excused
1187
+ };
1181
1188
  };
1182
1189
  async function shutdown(cause = "the service asked for it", request = {}) {
1183
1190
  if (request.code !== void 0 && !isExitCode(request.code)) throw new Error(`${description.name}: the exit code ${request.code} is not a whole number from 0 to 255`);
@@ -1209,10 +1216,11 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1209
1216
  };
1210
1217
  const phases = description.shutdown.phases;
1211
1218
  let on = "exit.request";
1219
+ let detail;
1212
1220
  for (const phase of phases) {
1213
1221
  if (exitState === "exited") break;
1214
1222
  const rank = stageRank(phase.name);
1215
- moveDrain(phase.name, on);
1223
+ moveDrain(phase.name, on, detail);
1216
1224
  if (rank > ABORT_AFTER) abortService();
1217
1225
  const work = [];
1218
1226
  const owned = components.phaseWork.get(phase.name) ?? [];
@@ -1224,6 +1232,7 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1224
1232
  for (const [, impl] of startedTransports) work.push(() => impl.stopIntake());
1225
1233
  }
1226
1234
  let cancelAbort = () => {};
1235
+ let excuse;
1227
1236
  if (phase.name === "drain") {
1228
1237
  const cap = valueIn(phase.cap, "ms");
1229
1238
  const cooperative = new AbortController();
@@ -1234,7 +1243,20 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1234
1243
  record("drain", drainState, drainState, "drain.cooperative-abort");
1235
1244
  });
1236
1245
  work.push(({ deadline }) => requests.drain(deadline));
1237
- for (const [, impl] of startedTransports) work.push(({ deadline }) => impl.drain(deadline, cooperative.signal));
1246
+ let transportsLeft = 0;
1247
+ for (const [, impl] of startedTransports) work.push(async ({ deadline }) => {
1248
+ transportsLeft += 1;
1249
+ try {
1250
+ await impl.drain(deadline, cooperative.signal);
1251
+ } finally {
1252
+ transportsLeft -= 1;
1253
+ }
1254
+ });
1255
+ excuse = (left) => {
1256
+ const holds = lateHolds(life);
1257
+ if (left > transportsLeft || transportsLeft > holds.length || holds.some((each) => each.running > 0)) return null;
1258
+ return `answers not flushed: ${holds.reduce((sum, each) => sum + each.unflushed, 0)}`;
1259
+ };
1238
1260
  }
1239
1261
  if (phase.name === "close transports") for (const [, impl] of startedTransports) work.push(() => impl.close());
1240
1262
  if (phase.name === "stop the poller") work.push(() => {
@@ -1259,12 +1281,13 @@ const lifecycle = (description, runtime, components, options = {}, bind = () =>
1259
1281
  await port.flush?.();
1260
1282
  });
1261
1283
  else for (const each of owned) work.push(each);
1262
- const inTime = await runPhase(phase, phase.name === "fail readiness", work);
1284
+ const { inTime, excused } = await runPhase(phase, phase.name === "fail readiness", work, excuse);
1263
1285
  cancelAbort();
1264
- if (!inTime) fail(`the phase ${phase.name} did not end in its cap of ${valueIn(phase.cap, "s")} s`);
1286
+ if (!inTime && excused === null) fail(`the phase ${phase.name} did not end in its cap of ${valueIn(phase.cap, "s")} s`);
1265
1287
  on = inTime ? "phase.done" : "phase.cap";
1288
+ detail = inTime || excused === null ? void 0 : excused;
1266
1289
  }
1267
- if (exitState !== "exited") moveDrain("done", on);
1290
+ if (exitState !== "exited") moveDrain("done", on, detail);
1268
1291
  abortService();
1269
1292
  runtime.stop();
1270
1293
  components.stopPollers();