signalk-questdb 1.5.0 → 1.5.2

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/README.md CHANGED
@@ -49,6 +49,13 @@ Three tables, all with WAL mode, daily partitioning, and deduplication:
49
49
  | `signalk_str` | String values | `ts`, `path` (SYMBOL), `context` (SYMBOL), `value_str` (VARCHAR) |
50
50
  | `signalk_position` | Positions | `ts`, `context` (SYMBOL), `lat` (DOUBLE), `lon` (DOUBLE) |
51
51
 
52
+ `ts` is the **server receive time**, not the timestamp a source claims. Marine
53
+ sources carry independent clocks, and storing their timestamps makes commits
54
+ land out of order — QuestDB then rewrites partition tails on every merge
55
+ (observed as >3000x write amplification). Receive time keeps ingestion
56
+ append-only; the millisecond difference is far below the sampling resolution,
57
+ and a device with a broken clock gets more accurate history, not less.
58
+
52
59
  ## History API
53
60
 
54
61
  ### v2 (REST -- `/signalk/v2/api/history/`)
@@ -153,8 +160,10 @@ QuestDB you point it at via `QuestDB host` + the HTTP/ILP ports.
153
160
 
154
161
  ## QuestDB Web Console
155
162
 
156
- QuestDB ships a web console (SQL editor + import UI) on its HTTP port. On the
157
- Signal K host it is at:
163
+ QuestDB ships a web console (SQL editor + import UI) on its HTTP port. See the
164
+ **[Web Console user guide](doc/web-console.md)** for an end-user walkthrough
165
+ with ready-to-paste sample queries (speed in knots, temperatures in °C,
166
+ distance per day, track export, and more). On the Signal K host it is at:
158
167
 
159
168
  ```
160
169
  http://localhost:9000
@@ -0,0 +1,2 @@
1
+ export type DeltaRoute = "number" | "string" | "position" | null;
2
+ export declare function routeDeltaValue(path: string, value: unknown): DeltaRoute;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.routeDeltaValue = routeDeltaValue;
4
+ function routeDeltaValue(path, value) {
5
+ if (typeof value === "number")
6
+ return "number";
7
+ if (typeof value === "string")
8
+ return "string";
9
+ if (path === "navigation.position" &&
10
+ value !== null &&
11
+ typeof value === "object" &&
12
+ "latitude" in value &&
13
+ "longitude" in value &&
14
+ Number.isFinite(value.latitude) &&
15
+ Number.isFinite(value.longitude))
16
+ return "position";
17
+ return null;
18
+ }
19
+ //# sourceMappingURL=delta-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta-routing.js","sourceRoot":"","sources":["../src/delta-routing.ts"],"names":[],"mappings":";;AAUA,0CAcC;AAdD,SAAgB,eAAe,CAAC,IAAY,EAAE,KAAc;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,IACE,IAAI,KAAK,qBAAqB;QAC9B,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,UAAU,IAAI,KAAK;QACnB,WAAW,IAAI,KAAK;QACpB,MAAM,CAAC,QAAQ,CAAE,KAA+B,CAAC,QAAQ,CAAC;QAC1D,MAAM,CAAC,QAAQ,CAAE,KAAgC,CAAC,SAAS,CAAC;QAE5D,OAAO,UAAU,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -11,6 +11,7 @@ export declare class ILPWriter {
11
11
  private connected;
12
12
  private connecting;
13
13
  private stopped;
14
+ private lastNanos;
14
15
  private connectedAt;
15
16
  private consecutiveFlaps;
16
17
  private droppedLines;
@@ -40,12 +41,13 @@ export declare class ILPWriter {
40
41
  private markUnhealthy;
41
42
  private markHealthy;
42
43
  private scheduleReconnect;
43
- write(path: string, context: string, value: number, timestamp: Date): void;
44
- writeString(path: string, context: string, value: string, timestamp: Date): void;
45
- writePosition(path: string, context: string, position: {
44
+ private nextNanos;
45
+ write(path: string, context: string, value: number, timestamp?: Date): void;
46
+ writeString(path: string, context: string, value: string, timestamp?: Date): void;
47
+ writePosition(context: string, position: {
46
48
  latitude: number;
47
49
  longitude: number;
48
- }, timestamp: Date): void;
50
+ }, timestamp?: Date): void;
49
51
  private enqueue;
50
52
  private requeueFront;
51
53
  private enforceBufferCap;
@@ -85,6 +85,17 @@ class ILPWriter {
85
85
  connected = false;
86
86
  connecting = false;
87
87
  stopped = false;
88
+ // Last timestamp handed out, in nanoseconds. Rows are stamped at write time
89
+ // with the server clock (see the delta handler), but `Date` is only
90
+ // millisecond-resolution while the `signalk`/`signalk_str` tables dedup on
91
+ // KEYS(ts, path, context). Two writes for the same path within one
92
+ // millisecond would collide and the later would upsert over the earlier —
93
+ // silent data loss for unthrottled (samplingRate 0) or bursty paths. QuestDB
94
+ // stores microsecond resolution, so advancing this counter by at least 1µs
95
+ // per write keeps every row's `ts` distinct without moving the visible
96
+ // millisecond. It only ratchets forward: a same/earlier wall-clock reading
97
+ // is bumped past the last one, so ingestion also stays strictly ordered.
98
+ lastNanos = 0n;
88
99
  // Wall-clock ms when the current socket's connect callback fired. Read in the
89
100
  // `close` handler to decide whether the connection was stable (reset backoff)
90
101
  // or an instant flap (grow backoff). Reset to 0 on every close.
@@ -246,16 +257,38 @@ class ILPWriter {
246
257
  });
247
258
  }, this.reconnectDelay);
248
259
  }
260
+ // Strictly-increasing nanosecond timestamp for the next row. Starts from the
261
+ // server clock but never repeats or goes backwards within one writer, so
262
+ // same-millisecond writes to a dedup table don't collide (see `lastNanos`).
263
+ // An explicit `Date` (used by tests that assert exact ILP lines) is honoured
264
+ // verbatim, but still advances the floor so it can't collide with a later
265
+ // omitted-timestamp write.
266
+ nextNanos(explicit) {
267
+ if (explicit) {
268
+ const explicitNanos = BigInt(explicit.getTime()) * 1000000n;
269
+ if (explicitNanos > this.lastNanos)
270
+ this.lastNanos = explicitNanos;
271
+ return explicitNanos;
272
+ }
273
+ const nowNanos = BigInt(Date.now()) * 1000000n;
274
+ // Advance by at least 1µs (QuestDB's storage resolution) past the last
275
+ // value so the microsecond QuestDB persists is always distinct.
276
+ this.lastNanos =
277
+ nowNanos > this.lastNanos ? nowNanos : this.lastNanos + 1000n;
278
+ return this.lastNanos;
279
+ }
249
280
  write(path, context, value, timestamp) {
250
- const ts = BigInt(timestamp.getTime()) * 1000000n;
281
+ const ts = this.nextNanos(timestamp);
251
282
  this.enqueue(`signalk,path=${escapeTag(path)},context=${escapeTag(context)} value=${value} ${ts}\n`);
252
283
  }
253
284
  writeString(path, context, value, timestamp) {
254
- const ts = BigInt(timestamp.getTime()) * 1000000n;
285
+ const ts = this.nextNanos(timestamp);
255
286
  this.enqueue(`signalk_str,path=${escapeTag(path)},context=${escapeTag(context)} value_str="${escapeFieldString(value)}" ${ts}\n`);
256
287
  }
257
- writePosition(path, context, position, timestamp) {
258
- const ts = BigInt(timestamp.getTime()) * 1000000n;
288
+ // signalk_position has no path column — it holds navigation.position rows
289
+ // exclusively (the caller enforces that), keyed on ts+context.
290
+ writePosition(context, position, timestamp) {
291
+ const ts = this.nextNanos(timestamp);
259
292
  this.enqueue(`signalk_position,context=${escapeTag(context)} lat=${position.latitude},lon=${position.longitude} ${ts}\n`);
260
293
  }
261
294
  enqueue(line) {
@@ -1 +1 @@
1
- {"version":3,"file":"ilp-writer.js","sourceRoot":"","sources":["../src/ilp-writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ilp-writer.js","sourceRoot":"","sources":["../src/ilp-writer.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ const minimatch_1 = require("minimatch");
37
37
  const ilp_writer_1 = require("./ilp-writer");
38
38
  const query_client_1 = require("./query-client");
39
39
  const schema_1 = require("./config/schema");
40
+ const delta_routing_1 = require("./delta-routing");
40
41
  const history_v2_1 = require("./history-v2");
41
42
  const history_v1_1 = require("./history-v1");
42
43
  const retention_1 = require("./retention");
@@ -123,6 +124,20 @@ module.exports = (app) => {
123
124
  // could resurrect a just-purged container. A simple promise-chain mutex:
124
125
  // each call waits for the previous to settle, then runs.
125
126
  let lifecycleChain = Promise.resolve();
127
+ // Bumped by stop() and purge before they tear down. Lifecycle work captures
128
+ // the value when it is ENQUEUED and re-checks it when it actually runs (and
129
+ // after its long awaits): startAbort can only cancel the start that is
130
+ // currently executing, so without this a start/update still waiting behind
131
+ // the chain — or one parked in a long await — would run to completion after
132
+ // teardown and resurrect the resources that were just torn down.
133
+ let lifecycleGeneration = 0;
134
+ // True only between a FULLY completed start (providers, stream
135
+ // subscription, and timers all registered) and the next stop/purge.
136
+ // queryClient/writer are not usable as a running sentinel: they are
137
+ // created partway through startup, so a start that fails after that point
138
+ // (health-wait exhausted, ensureTables/connect threw) leaves them non-null
139
+ // on a plugin that never came up.
140
+ let pluginRunning = false;
126
141
  const withLifecycleLock = (fn) => {
127
142
  const run = lifecycleChain.then(fn, fn);
128
143
  // Keep the chain alive regardless of this op's outcome.
@@ -244,12 +259,15 @@ module.exports = (app) => {
244
259
  * QuestDB is not published beyond loopback / the shared network.
245
260
  * - on: publish the configured host ports on 0.0.0.0 (for LAN / separate-
246
261
  * Docker Grafana) on the shared `networkName`; the Signal K process
247
- * reaches them on the host loopback (bare-metal) or via the
248
- * host.containers.internal gateway (containerized SK).
262
+ * reaches them on the host loopback (bare-metal) or containerized —
263
+ * on whichever of loopback/host.containers.internal actually answers
264
+ * (probed, since the right one depends on the SK container's network
265
+ * mode; see resolveLanExposureHost).
249
266
  *
250
- * `endpoints` is resolved AFTER ensureRunning() on the signalkAccessiblePorts
251
- * path (the host port is allocated by then); on the LAN path it is already
252
- * known, so the returned resolver just echoes it.
267
+ * `endpoints` is resolved AFTER ensureRunning() on both paths: the
268
+ * signalkAccessiblePorts path needs the allocated host port, and the LAN
269
+ * path's probe needs a running QuestDB. The returned resolver defers the
270
+ * work into the closure (memoized on the LAN path) for exactly that reason.
253
271
  */
254
272
  async function applyQuestdbNetworking(config, containers, name, containerConfig) {
255
273
  const httpPort = config.questdbHttpPort ?? questdb_endpoint_1.QUESTDB_INTERNAL_HTTP_PORT;
@@ -274,14 +292,22 @@ module.exports = (app) => {
274
292
  await containers.ensureNetwork(config.networkName);
275
293
  containerConfig.networkMode = config.networkName;
276
294
  }
277
- // A 0.0.0.0-published port is reachable from a containerized SK via the
278
- // host gateway; a loopback-only one (or any bare-metal/old-container
279
- // case) is reached on 127.0.0.1.
280
- const skHost = config.exposeToContainers
281
- ? await (0, questdb_endpoint_1.resolveLanExposureHost)(containers, (msg) => app.debug(msg))
282
- : "127.0.0.1";
283
- const endpoints = (0, questdb_endpoint_1.lanExposureEndpoints)(skHost, httpPort, ilpPort);
284
- return async () => endpoints;
295
+ // A 0.0.0.0-published port is reached on 127.0.0.1 or via the host
296
+ // gateway depending on how a containerized SK is networked — resolved
297
+ // by probing, which only works once QuestDB is up. The resolver runs
298
+ // after ensureRunning() on both call sites, so defer the probe into
299
+ // the closure (and memoize: the endpoint can't change while the
300
+ // container config that produced it is live).
301
+ let endpoints = null;
302
+ return async () => {
303
+ if (!endpoints) {
304
+ const skHost = config.exposeToContainers
305
+ ? await (0, questdb_endpoint_1.resolveLanExposureHost)(containers, httpPort, (msg) => app.debug(msg))
306
+ : "127.0.0.1";
307
+ endpoints = (0, questdb_endpoint_1.lanExposureEndpoints)(skHost, httpPort, ilpPort);
308
+ }
309
+ return endpoints;
310
+ };
285
311
  }
286
312
  // Default path: signalk-container owns the networking. After the container
287
313
  // is up we additionally attach it to `networkName` so the companion
@@ -359,6 +385,9 @@ module.exports = (app) => {
359
385
  }
360
386
  }
361
387
  async function runStartInner(config, signal) {
388
+ // Not running until THIS start completes — a restart that reuses the
389
+ // slot must not leave a stale true from the previous run.
390
+ pluginRunning = false;
362
391
  // Older saved configs miss keys added since (pathFilter, samplingRates);
363
392
  // the per-delta pipeline dereferences them per delta, so normalize once
364
393
  // at the boundary.
@@ -463,7 +492,13 @@ module.exports = (app) => {
463
492
  if (signal.aborted)
464
493
  return;
465
494
  app.debug("QuestDB container ready");
466
- questdbEndpoints = await resolveEndpoints();
495
+ // The LAN path probes for the reachable host in here (up to its
496
+ // retry deadline), so this await is long enough for a stop/purge to
497
+ // preempt us — re-check before committing the result.
498
+ const resolved = await resolveEndpoints();
499
+ if (signal.aborted)
500
+ return;
501
+ questdbEndpoints = resolved;
467
502
  }
468
503
  catch (err) {
469
504
  app.debug("ensureRunning failed:", err);
@@ -542,7 +577,7 @@ module.exports = (app) => {
542
577
  const unsub = bus.onValue((delta) => {
543
578
  if (!writer)
544
579
  return;
545
- const { path, value, context, timestamp } = delta;
580
+ const { path, value, context } = delta;
546
581
  if (!path || value === undefined || value === null)
547
582
  return;
548
583
  const isSelf = context === app.selfContext;
@@ -554,19 +589,28 @@ module.exports = (app) => {
554
589
  return;
555
590
  if (isThrottled(path, config.samplingRates, config.defaultSamplingRate ?? 2000))
556
591
  return;
557
- const ts = timestamp ? new Date(timestamp) : new Date();
592
+ // Rows are stamped with the server receive time, deliberately NOT the
593
+ // delta's own timestamp: a boat is a set of independent clocks (GPS
594
+ // time, RTC-less devices, gateway latencies), and storing per-source
595
+ // timestamps makes commits land out of order. QuestDB then rewrites
596
+ // partition tails on every merge — observed in the field as >3000x
597
+ // write amplification (physical rows rewritten per row inserted),
598
+ // grinding SD cards and, before batching, stalling the WAL outright.
599
+ // The writer assigns the actual timestamp (strictly monotonic to the
600
+ // microsecond, so same-millisecond writes don't collide on the dedup
601
+ // key), so every commit is a pure append. A device with a broken clock
602
+ // even gets *more* accurate history. Re-sent ILP batches keep their
603
+ // original stamps (baked at write() time), so replay-idempotency holds.
558
604
  const ctx = isSelf ? "self" : context;
559
- if (typeof value === "number") {
560
- writer.write(path, ctx, value, ts);
605
+ const route = (0, delta_routing_1.routeDeltaValue)(path, value);
606
+ if (route === "number") {
607
+ writer.write(path, ctx, value);
561
608
  }
562
- else if (typeof value === "string") {
563
- writer.writeString(path, ctx, value, ts);
609
+ else if (route === "string") {
610
+ writer.writeString(path, ctx, value);
564
611
  }
565
- else if (value &&
566
- typeof value === "object" &&
567
- "latitude" in value &&
568
- "longitude" in value) {
569
- writer.writePosition(path, ctx, value, ts);
612
+ else if (route === "position") {
613
+ writer.writePosition(ctx, value);
570
614
  }
571
615
  });
572
616
  unsubscribes.push(unsub);
@@ -580,11 +624,25 @@ module.exports = (app) => {
580
624
  void healSchemaTables();
581
625
  }, SCHEMA_HEAL_INTERVAL_MS);
582
626
  app.setPluginStatus(`Recording to QuestDB at ${ilpHost}:${ilpPort}`);
627
+ // Everything is registered — only now does the plugin count as running.
628
+ // Any earlier return/throw leaves the flag false, so a half-started
629
+ // plugin rejects lifecycle-dependent requests like /api/update/apply.
630
+ pluginRunning = true;
583
631
  }
584
632
  // Public entry: serialize startup behind the lifecycle lock so a purge or
585
- // update can't interleave with it.
633
+ // update can't interleave with it. The generation is captured at enqueue: a
634
+ // stop()/purge landing while this start is still queued invalidates it
635
+ // before it begins — the window startAbort cannot cover, since the abort
636
+ // controller only exists once runStart() is executing.
586
637
  function asyncStart(config) {
587
- return withLifecycleLock(() => runStart(config));
638
+ const generation = lifecycleGeneration;
639
+ return withLifecycleLock(async () => {
640
+ if (generation !== lifecycleGeneration) {
641
+ app.debug("skipping queued start: plugin stopped while it waited");
642
+ return;
643
+ }
644
+ await runStart(config);
645
+ });
588
646
  }
589
647
  const plugin = {
590
648
  id: "signalk-questdb",
@@ -598,6 +656,15 @@ module.exports = (app) => {
598
656
  });
599
657
  },
600
658
  async stop() {
659
+ // Preempt lifecycle work: bump the generation so anything still queued
660
+ // behind the lifecycle chain (or parked in a long await that re-checks
661
+ // it) bails, and abort the currently-executing start so its post-await
662
+ // signal checks return before registering resources. Together these
663
+ // keep a slow start/update (container pull, LAN-host probe) from
664
+ // resurrecting a writer/client after this teardown ran.
665
+ lifecycleGeneration++;
666
+ startAbort?.abort();
667
+ pluginRunning = false;
601
668
  for (const unsub of unsubscribes) {
602
669
  try {
603
670
  unsub();
@@ -861,6 +928,10 @@ module.exports = (app) => {
861
928
  });
862
929
  router.post("/api/update/apply", async (_req, res) => {
863
930
  try {
931
+ // Captured at route entry, before ANY await: a stop() that lands
932
+ // during the release fetch below must already invalidate this
933
+ // update, not just one that lands after the lock is acquired.
934
+ const updateGeneration = lifecycleGeneration;
864
935
  const containers = globalThis.__signalk_containerManager;
865
936
  if (!containers || !containers.getRuntime()) {
866
937
  res.status(503).json({ error: "Container manager not available" });
@@ -884,9 +955,26 @@ module.exports = (app) => {
884
955
  const newTag = stable.tag_name;
885
956
  // Run the mutating update under the lifecycle lock so a concurrent
886
957
  // purge/start can't interleave with the pull + recreate + reconnect.
958
+ // The route-entry generation is re-checked at lock entry and after
959
+ // the long awaits: a stop() during the release fetch, the pull, or
960
+ // the LAN-host probe must not let this resume, reassign endpoints,
961
+ // and report success for a container the teardown already dealt
962
+ // with.
887
963
  const ilp = await withLifecycleLock(async () => {
964
+ const assertNotStopped = () => {
965
+ if (updateGeneration !== lifecycleGeneration)
966
+ throw new Error("plugin stopped while the update was running");
967
+ };
968
+ assertNotStopped();
969
+ // A generation match only proves no stop() happened since route
970
+ // entry — it can't tell that the plugin was already stopped (or
971
+ // never fully started) when the request arrived. Gate on the
972
+ // completed-start sentinel before pulling or recreating anything.
973
+ if (!pluginRunning)
974
+ throw new Error("plugin is not running");
888
975
  app.setPluginStatus(`Pulling QuestDB ${newTag}...`);
889
976
  await containers.pullImage(`questdb/questdb:${newTag}`);
977
+ assertNotStopped();
890
978
  if (currentConfig) {
891
979
  currentConfig.questdbVersion = newTag;
892
980
  await new Promise((resolve, reject) => {
@@ -932,11 +1020,15 @@ module.exports = (app) => {
932
1020
  };
933
1021
  const resolveUpdateEndpoints = await applyQuestdbNetworking(currentConfig ?? {}, containers, QUESTDB_CONTAINER_NAME, updateConfig);
934
1022
  // Clear any prior clamp before re-running so a no-longer-clamping
935
- // update doesn't leave a stale warning.
1023
+ // update doesn't leave a stale warning. The recreate must not
1024
+ // happen after a stop/purge teardown, so gate it on the
1025
+ // generation one more time.
1026
+ assertNotStopped();
936
1027
  ulimitClamp = null;
937
1028
  await containers.ensureRunning(QUESTDB_CONTAINER_NAME, updateConfig, {
938
1029
  onUlimitClamped,
939
1030
  });
1031
+ assertNotStopped();
940
1032
  // The QuestDB version just changed, so the cached wal_tables()
941
1033
  // column shape may no longer match (the errorTag/errorMessage
942
1034
  // columns could appear on an upgrade or disappear on a downgrade).
@@ -947,26 +1039,51 @@ module.exports = (app) => {
947
1039
  // current endpoint. The version bump keeps the same container name
948
1040
  // and networking, so the endpoint is stable — the existing
949
1041
  // QueryClient/ILPWriter stay valid (and the registered history
950
- // providers keep their reference).
951
- questdbEndpoints = await resolveUpdateEndpoints();
1042
+ // providers keep their reference). On the LAN path this await
1043
+ // probes for the reachable host, so it's long enough for a stop()
1044
+ // to land mid-flight — re-check before touching plugin state.
1045
+ const updatedEndpoints = await resolveUpdateEndpoints();
1046
+ assertNotStopped();
1047
+ questdbEndpoints = updatedEndpoints;
952
1048
  const { host: ilpHost, port: ilpPort } = questdbEndpoints.ilp;
953
1049
  // Wait for the recreated container to answer, then reconnect ILP.
954
- if (queryClient) {
1050
+ // Locals are captured because stop() nulls the module bindings:
1051
+ // reading them again mid-loop would throw, and the generation
1052
+ // checks after every await are what keep a concurrent teardown
1053
+ // from being resurrected by the reconnect.
1054
+ const client = queryClient;
1055
+ if (client) {
955
1056
  const deadline = Date.now() + 30000;
956
1057
  while (Date.now() < deadline) {
957
- if (await queryClient.isHealthy())
1058
+ assertNotStopped();
1059
+ if (await client.isHealthy())
958
1060
  break;
959
1061
  await new Promise((resolve) => setTimeout(resolve, 500));
960
1062
  }
961
1063
  }
962
- if (writer) {
1064
+ assertNotStopped();
1065
+ const ilpWriter = writer;
1066
+ if (ilpWriter) {
963
1067
  try {
964
- await writer.disconnect();
1068
+ await ilpWriter.disconnect();
965
1069
  }
966
1070
  catch {
967
1071
  /* ignore */
968
1072
  }
969
- await writer.connect();
1073
+ assertNotStopped();
1074
+ await ilpWriter.connect();
1075
+ // A stop() that landed during connect() has already nulled
1076
+ // `writer`, so nothing would ever disconnect this fresh
1077
+ // connection — close it ourselves before failing the update.
1078
+ if (updateGeneration !== lifecycleGeneration) {
1079
+ try {
1080
+ await ilpWriter.disconnect();
1081
+ }
1082
+ catch {
1083
+ /* ignore */
1084
+ }
1085
+ assertNotStopped();
1086
+ }
970
1087
  }
971
1088
  return { ilpHost, ilpPort };
972
1089
  });
@@ -1087,6 +1204,7 @@ module.exports = (app) => {
1087
1204
  if (teardownStarted)
1088
1205
  return;
1089
1206
  teardownStarted = true;
1207
+ pluginRunning = false;
1090
1208
  // Stop all activity against the container before it and its data go
1091
1209
  // away — otherwise the retention timer keeps issuing DROP PARTITION
1092
1210
  // against a removed container and the writer keeps trying to connect.
@@ -1115,13 +1233,16 @@ module.exports = (app) => {
1115
1233
  // Purge is the RECOVERY action, so it must make progress even when a
1116
1234
  // start is wedged on a never-settling container call. Step 1: signal
1117
1235
  // any in-flight start to bail (its post-await checks return without
1118
- // creating resources). Step 2: try to acquire the lifecycle lock so
1236
+ // creating resources) and bump the generation so a start/update
1237
+ // still QUEUED behind the chain bails at entry instead of running
1238
+ // after the teardown. Step 2: try to acquire the lifecycle lock so
1119
1239
  // we still serialize against a start/update that IS progressing —
1120
1240
  // but only for a bounded time. If the lock isn't free within
1121
1241
  // PURGE_LOCK_TIMEOUT_MS (a truly hung start that ignored the abort
1122
1242
  // and never released it), run the teardown anyway. Interleaving risk
1123
1243
  // is minimal because we already aborted the start, so any start that
1124
1244
  // later wakes up returns early instead of recreating the container.
1245
+ lifecycleGeneration++;
1125
1246
  startAbort?.abort();
1126
1247
  let lockedTeardownSettled = false;
1127
1248
  let lockedTeardownError;