@harperfast/harper 5.2.4 → 5.2.6

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.
Files changed (72) hide show
  1. package/bin/restart.ts +48 -23
  2. package/bin/restartExitWatchdog.ts +99 -0
  3. package/bin/run.ts +16 -10
  4. package/components/status/crossThread.ts +69 -44
  5. package/components/status/types.ts +1 -0
  6. package/dist/bin/restart.js +39 -21
  7. package/dist/bin/restart.js.map +1 -1
  8. package/dist/bin/restartExitWatchdog.d.ts +5 -0
  9. package/dist/bin/restartExitWatchdog.js +100 -0
  10. package/dist/bin/restartExitWatchdog.js.map +1 -0
  11. package/dist/bin/run.d.ts +2 -0
  12. package/dist/bin/run.js +17 -10
  13. package/dist/bin/run.js.map +1 -1
  14. package/dist/components/status/crossThread.d.ts +1 -0
  15. package/dist/components/status/crossThread.js +57 -41
  16. package/dist/components/status/crossThread.js.map +1 -1
  17. package/dist/components/status/types.d.ts +1 -0
  18. package/dist/resources/DatabaseTransaction.d.ts +46 -1
  19. package/dist/resources/DatabaseTransaction.js +173 -9
  20. package/dist/resources/DatabaseTransaction.js.map +1 -1
  21. package/dist/resources/LMDBTransaction.d.ts +2 -0
  22. package/dist/resources/LMDBTransaction.js +7 -1
  23. package/dist/resources/LMDBTransaction.js.map +1 -1
  24. package/dist/resources/Resource.js +5 -4
  25. package/dist/resources/Resource.js.map +1 -1
  26. package/dist/resources/ResourceInterface.d.ts +20 -0
  27. package/dist/resources/ResourceInterface.js.map +1 -1
  28. package/dist/resources/Table.js +56 -9
  29. package/dist/resources/Table.js.map +1 -1
  30. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +11 -0
  31. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +153 -51
  32. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  33. package/dist/resources/transaction.js +4 -2
  34. package/dist/resources/transaction.js.map +1 -1
  35. package/dist/server/itc/serverHandlers.js +2 -1
  36. package/dist/server/itc/serverHandlers.js.map +1 -1
  37. package/dist/server/threads/manageThreads.d.ts +2 -0
  38. package/dist/server/threads/manageThreads.js +37 -5
  39. package/dist/server/threads/manageThreads.js.map +1 -1
  40. package/dist/utility/processManagement/processManagement.js +21 -3
  41. package/dist/utility/processManagement/processManagement.js.map +1 -1
  42. package/npm-shrinkwrap.json +2 -2
  43. package/package.json +1 -1
  44. package/resources/DESIGN.md +19 -18
  45. package/resources/DatabaseTransaction.ts +187 -17
  46. package/resources/LMDBTransaction.ts +7 -1
  47. package/resources/Resource.ts +6 -5
  48. package/resources/ResourceInterface.ts +20 -0
  49. package/resources/Table.ts +60 -8
  50. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +153 -54
  51. package/resources/transaction.ts +5 -3
  52. package/schema.graphql +8 -3
  53. package/server/DESIGN.md +6 -0
  54. package/server/itc/serverHandlers.js +2 -1
  55. package/server/threads/manageThreads.js +34 -5
  56. package/studio/web/assets/{Chat-BvOlYIhe.js → Chat-BNHY84-Z.js} +1 -1
  57. package/studio/web/assets/{FloatingChat-fCJiJCH2.js → FloatingChat-BbztUci0.js} +3 -3
  58. package/studio/web/assets/{apiToken-bKwPv0uN.js → apiToken-D0x_S8Wq.js} +1 -1
  59. package/studio/web/assets/{applications-BZ47njJ_.js → applications-BU83fSEG.js} +1 -1
  60. package/studio/web/assets/{index-DF1fSnXV.js → index-BQB9R8Ug.js} +4 -4
  61. package/studio/web/assets/{index.lazy-DWt8ubrr.js → index.lazy-CtErm5q7.js} +3 -3
  62. package/studio/web/assets/{notifications-B5ZBOni1.js → notifications-B8rDagJM.js} +1 -1
  63. package/studio/web/assets/{notifications-gVx0lA8H.js → notifications-CPwYKDDH.js} +1 -1
  64. package/studio/web/assets/{profile-D7g0x28A.js → profile-D0hB1xdm.js} +1 -1
  65. package/studio/web/assets/{regions-DRkhlkiD.js → regions-y_jxsBpN.js} +1 -1
  66. package/studio/web/assets/{setComponentFile-DQkkobG5.js → setComponentFile-DSHbURTI.js} +1 -1
  67. package/studio/web/assets/{setup-nf-EOheW.js → setup-J5SrM1Kp.js} +1 -1
  68. package/studio/web/assets/{status-B2tOM3RF.js → status-IoJkDzlx.js} +1 -1
  69. package/studio/web/assets/{swagger-ui-react-UktCouQ0.js → swagger-ui-react-BlgSS25O.js} +1 -1
  70. package/studio/web/assets/{useEntityRestURL-alqAn3dT.js → useEntityRestURL-qfahJnvr.js} +1 -1
  71. package/studio/web/index.html +1 -1
  72. package/utility/processManagement/processManagement.js +19 -3
package/bin/restart.ts CHANGED
@@ -6,13 +6,19 @@ import * as hdbTerms from '../utility/hdbTerms.ts';
6
6
  import hdbLogger from '../utility/logging/harper_logger.ts';
7
7
  import * as processMan from '../utility/processManagement/processManagement.js';
8
8
  import { compactOnStart } from './copyDb.ts';
9
- import { restartWorkers, onMessageByType, shutdownWorkersNow } from '../server/threads/manageThreads.js';
9
+ import {
10
+ beginProcessShutdown,
11
+ restartWorkers,
12
+ onMessageByType,
13
+ shutdownWorkersNow,
14
+ } from '../server/threads/manageThreads.js';
10
15
  import { handleHDBError, hdbErrors } from '../utility/errors/hdbError.ts';
11
16
  const { HTTP_STATUS_CODES } = hdbErrors;
12
17
  import * as envMgr from '../utility/environment/environmentManager.ts';
13
18
  import * as path from 'node:path';
14
- import { unlinkSync } from 'node:fs';
19
+ import { rmSync } from 'node:fs';
15
20
  import { getThisNodeName } from '../server/nodeName.ts';
21
+ import { armRestartExitWatchdog } from './restartExitWatchdog.ts';
16
22
  envMgr.initSync();
17
23
 
18
24
  const RESTART_RESPONSE = `Restarting Harper. This may take up to ${hdbTerms.RESTART_TIMEOUT_MS / 1000} seconds.`;
@@ -56,30 +62,49 @@ async function restart(req: any) {
56
62
  if (isMainThread) {
57
63
  hdbLogger.notify(RESTART_RESPONSE);
58
64
 
59
- if (envMgr.get(hdbTerms.CONFIG_PARAMS.STORAGE_COMPACTONSTART)) await compactOnStart();
65
+ if (envMgr.get(hdbTerms.CONFIG_PARAMS.STORAGE_COMPACTONSTART)) {
66
+ hdbLogger.info('Compacting storage before restart; the restart timeout begins after compaction completes');
67
+ await compactOnStart();
68
+ }
60
69
 
61
70
  setTimeout(async () => {
62
- // It seems like you should just be able to start the other process and kill this process and everything should
63
- // be cleaned up, however that doesn't work for some reason; the socket listening fds somehow get transferred to the
64
- // child process if they are not explicitly closed. And when transferred they are orphaned listening, accepting
65
- // connections and hanging. So we need to explicitly close down all the workers and then start the new process
66
- // and shut down.
67
- hdbLogger.debug('Shutdown workers');
68
- await shutdownWorkersNow();
69
- const { closeServers } = require('../server/threads/threadServer.js');
70
- await closeServers();
71
- await processMan.cleanupChildrenProcesses(false);
72
- // remove pid file so it doesn't trip up the launch
73
- unlinkSync(path.join(envMgr.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), hdbTerms.HDB_PID_FILE));
74
- hdbLogger.debug('Starting new process...');
75
- if (process.env.HARPER_EXIT_ON_RESTART) {
76
- // use this to exit the process so that it will be restarted by the
77
- // PM/container/orchestrator.
78
- hdbLogger.warn('Exiting Harper process to trigger a container restart');
79
- process.exit(0);
71
+ try {
72
+ // Latch before the watchdog handshake, not at shutdownWorkersNow() below: a component's
73
+ // debounced requestRestart() landing in between would otherwise reload root components and
74
+ // pre-start an HTTP replacement into a process that is already exiting.
75
+ beginProcessShutdown();
76
+ // Off Linux the watchdog can never arm, and armRestartExitWatchdog() has already warned.
77
+ if (
78
+ process.env.HARPER_EXIT_ON_RESTART &&
79
+ !(await armRestartExitWatchdog(hdbTerms.RESTART_TIMEOUT_MS)) &&
80
+ process.platform === 'linux'
81
+ )
82
+ hdbLogger.error('Restart exit watchdog is unavailable; restart teardown is unbounded');
83
+ // It seems like you should just be able to start the other process and kill this process and everything should
84
+ // be cleaned up, however that doesn't work for some reason; the socket listening fds somehow get transferred to the
85
+ // child process if they are not explicitly closed. And when transferred they are orphaned listening, accepting
86
+ // connections and hanging. So we need to explicitly close down all the workers and then start the new process
87
+ // and shut down.
88
+ hdbLogger.debug('Shutdown workers');
89
+ await shutdownWorkersNow();
90
+ const { closeServers } = require('../server/threads/threadServer.js');
91
+ await closeServers();
92
+ await processMan.cleanupChildrenProcesses(false);
93
+ // remove pid file so it doesn't trip up the launch
94
+ rmSync(path.join(envMgr.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), hdbTerms.HDB_PID_FILE), { force: true });
95
+ hdbLogger.debug('Starting new process...');
96
+ if (process.env.HARPER_EXIT_ON_RESTART) {
97
+ // use this to exit the process so that it will be restarted by the
98
+ // PM/container/orchestrator.
99
+ hdbLogger.warn('Exiting Harper process to trigger a container restart');
100
+ process.exit(0);
101
+ }
102
+ // now launch the new process and exit this process
103
+ await require('./run').launch(true);
104
+ } catch (error) {
105
+ hdbLogger.fatal('Restart teardown failed; exiting Harper', error);
106
+ process.exit(1);
80
107
  }
81
- // now launch the new process and exit this process
82
- require('./run').launch(true);
83
108
  }, 50); // can't await this because it is going to do an exit(), but wait for 50ms so we give the HTTP thread a
84
109
  // chance to return a response
85
110
  } else {
@@ -0,0 +1,99 @@
1
+ import { spawn, type ChildProcess } from 'node:child_process';
2
+
3
+ import hdbLogger from '../utility/logging/harper_logger.ts';
4
+
5
+ // Every way the script below can give up is a silent `exit 0` — an unreadable procfs (a sandbox that
6
+ // does not mount /proc) or a base image without `sleep`. Reporting the watchdog armed in exactly
7
+ // those environments would replace the operator's one signal that teardown is unbounded with a claim
8
+ // that it is not, so arming waits for the script to prove both facilities before it says so.
9
+ export const WATCHDOG_READY_TOKEN = 'harper-restart-exit-watchdog-ready';
10
+ const WATCHDOG_READY_TIMEOUT_MS = 5000;
11
+
12
+ export const WATCHDOG_SCRIPT = `
13
+ parent_pid=$1
14
+ delay_seconds=$2
15
+ [ "$parent_pid" -gt 1 ] || exit 0
16
+ command -v sleep > /dev/null 2>&1 || exit 0
17
+ # Shell PPID variables are snapshots; procfs reflects reparenting after Harper exits.
18
+ read -r _ _ _ current_parent _ < "/proc/$$/stat" || exit 0
19
+ [ "$current_parent" = "$parent_pid" ] || exit 0
20
+ echo "${WATCHDOG_READY_TOKEN}"
21
+ sleep "$delay_seconds" || exit 0
22
+ read -r _ _ _ current_parent _ < "/proc/$$/stat" || exit 0
23
+ [ "$current_parent" = "$parent_pid" ] || exit 0
24
+ echo "harper: restart exit watchdog force-killing pid $parent_pid after $delay_seconds seconds" >&2
25
+ kill -KILL "$parent_pid"
26
+ `;
27
+
28
+ export async function armRestartExitWatchdog(timeoutMs: number) {
29
+ if (process.platform !== 'linux') {
30
+ hdbLogger.warn('Restart exit watchdog is only available on Linux');
31
+ return false;
32
+ }
33
+ if (process.pid <= 1) {
34
+ hdbLogger.warn('Restart exit watchdog requires Harper to run below a container init process');
35
+ return false;
36
+ }
37
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
38
+ hdbLogger.warn('Restart exit watchdog requires a positive finite timeout');
39
+ return false;
40
+ }
41
+
42
+ try {
43
+ const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
44
+ const watchdogPath = ['/usr/bin', '/bin', process.env.PATH].filter(Boolean).join(':');
45
+ const watchdog = spawn(
46
+ '/bin/sh',
47
+ ['-c', WATCHDOG_SCRIPT, 'harper-restart-exit-watchdog', String(process.pid), String(timeoutSeconds)],
48
+ {
49
+ env: { PATH: watchdogPath },
50
+ stdio: ['ignore', 'pipe', 'inherit'],
51
+ windowsHide: true,
52
+ }
53
+ );
54
+ // Before the pid check: a spawn failure (EMFILE on the very wedged process this bounds) emits
55
+ // 'error' asynchronously, and an unhandled one becomes an uncaughtException mid-teardown.
56
+ watchdog.once('error', (error) => hdbLogger.warn('Restart exit watchdog failed', error));
57
+ if (watchdog.pid === undefined) {
58
+ hdbLogger.warn('Restart exit watchdog failed to start');
59
+ return false;
60
+ }
61
+ watchdog.unref();
62
+
63
+ const ready = await waitForWatchdogReady(watchdog);
64
+ // The token is the last thing the script writes to stdout; the force-kill diagnostic goes to the
65
+ // inherited stderr so it survives a wedged Harper event loop.
66
+ watchdog.stdout.destroy();
67
+ if (!ready) {
68
+ watchdog.kill('SIGKILL');
69
+ hdbLogger.warn('Restart exit watchdog could not confirm it was armed');
70
+ return false;
71
+ }
72
+ hdbLogger.info(`Restart exit watchdog armed for ${timeoutSeconds} seconds`);
73
+ return true;
74
+ } catch (error) {
75
+ hdbLogger.warn('Restart exit watchdog failed to start', error);
76
+ return false;
77
+ }
78
+ }
79
+
80
+ export function waitForWatchdogReady(watchdog: ChildProcess, timeoutMs = WATCHDOG_READY_TIMEOUT_MS) {
81
+ return new Promise<boolean>((resolve) => {
82
+ let settled = false;
83
+ const settle = (value: boolean) => {
84
+ if (settled) return;
85
+ settled = true;
86
+ clearTimeout(readyTimer);
87
+ resolve(value);
88
+ };
89
+ const readyTimer = setTimeout(() => settle(false), timeoutMs);
90
+ readyTimer.unref();
91
+ let output = '';
92
+ watchdog.stdout?.on('data', (chunk) => {
93
+ output += chunk;
94
+ if (output.includes(WATCHDOG_READY_TOKEN)) settle(true);
95
+ });
96
+ watchdog.once('error', () => settle(false));
97
+ watchdog.once('exit', () => settle(false));
98
+ });
99
+ }
package/bin/run.ts CHANGED
@@ -23,6 +23,7 @@ import { compactOnStart, migrateOnStart } from './copyDb.ts';
23
23
  import minimist from 'minimist';
24
24
  import * as keys from '../security/keys.ts';
25
25
  import { startHTTPThreads } from '../server/threads/socketRouter.ts';
26
+ import { beginProcessShutdown } from '../server/threads/manageThreads.js';
26
27
  import * as hdbInfoController from '../dataLayer/hdbInfoController.ts';
27
28
  import { isReadOnlyMode } from '../resources/databases.ts';
28
29
  import { getThisNodeName } from '../server/nodeName.ts';
@@ -51,21 +52,25 @@ function addUnhandleRejectionListener() {
51
52
  function addExitListeners() {
52
53
  if (!skipExitListeners) {
53
54
  const removeHdbPid = () => {
54
- fs.removeSync(path.join(env.get(terms.CONFIG_PARAMS.ROOTPATH), terms.HDB_PID_FILE));
55
- process.exit(0);
55
+ try {
56
+ fs.removeSync(path.join(env.get(terms.CONFIG_PARAMS.ROOTPATH), terms.HDB_PID_FILE));
57
+ } catch (error) {
58
+ hdbLogger.error('Unable to remove the Harper pid file during shutdown', error);
59
+ }
56
60
  };
61
+ // Forward defence, not load-bearing today: nothing below can reach a worker start.
57
62
  process.on('exit', () => {
63
+ beginProcessShutdown();
58
64
  removeHdbPid();
59
65
  });
60
- process.on('SIGINT', () => {
66
+ const exit = () => {
67
+ beginProcessShutdown();
61
68
  removeHdbPid();
62
- });
63
- process.on('SIGQUIT', () => {
64
- removeHdbPid();
65
- });
66
- process.on('SIGTERM', () => {
67
- removeHdbPid();
68
- });
69
+ process.exit(0);
70
+ };
71
+ process.on('SIGINT', exit);
72
+ process.on('SIGQUIT', exit);
73
+ process.on('SIGTERM', exit);
69
74
  }
70
75
  }
71
76
 
@@ -262,6 +267,7 @@ async function launch(exit = true) {
262
267
  }
263
268
  }
264
269
 
270
+ export { addExitListeners };
265
271
  export { launch };
266
272
  export { main };
267
273
  export { startupLog };
@@ -6,7 +6,11 @@
6
6
  */
7
7
 
8
8
  import { sendItcEvent } from '../../server/threads/itc.js';
9
- import { getWorkerIndex, onMessageByType, getWorkerCount } from '../../server/threads/manageThreads.js';
9
+ import {
10
+ getWorkerIndex,
11
+ onMessageByType,
12
+ getEligibleBroadcastRecipientThreadIds,
13
+ } from '../../server/threads/manageThreads.js';
10
14
  import { ITC_EVENT_TYPES } from '../../utility/hdbTerms.ts';
11
15
  import { loggerWithTag } from '../../utility/logging/logger.ts';
12
16
  import { ComponentStatusRegistry } from './ComponentStatusRegistry.ts';
@@ -21,13 +25,27 @@ import {
21
25
  import { ITCError } from './errors.ts';
22
26
 
23
27
  const logger = loggerWithTag('componentStatus.crossThread');
28
+ const THREAD_IDENTITY_SEPARATOR = '#thread-';
29
+ const STATUS_PRIORITY = [
30
+ COMPONENT_STATUS_LEVELS.ERROR,
31
+ COMPONENT_STATUS_LEVELS.WARNING,
32
+ COMPONENT_STATUS_LEVELS.LOADING,
33
+ COMPONENT_STATUS_LEVELS.UNKNOWN,
34
+ COMPONENT_STATUS_LEVELS.HEALTHY,
35
+ ];
36
+
37
+ function stripThreadIdentity(statusKey: string): string {
38
+ const threadLabelStart = statusKey.lastIndexOf('@') + 1;
39
+ const identityIndex = statusKey.indexOf(THREAD_IDENTITY_SEPARATOR, threadLabelStart);
40
+ return identityIndex === -1 ? statusKey : statusKey.substring(0, identityIndex);
41
+ }
24
42
 
25
43
  /**
26
44
  * CrossThreadStatusCollector Class
27
45
  * Handles collection of component status from all worker threads
28
46
  */
29
47
  export class CrossThreadStatusCollector {
30
- private awaitingResponses = new Map<number, Array<WorkerComponentStatuses>>();
48
+ private awaitingResponses = new Map<number, Map<number, WorkerComponentStatuses>>();
31
49
  private responseCheckers = new Map<number, () => void>();
32
50
  private nextRequestId = 1;
33
51
  private listenerAttached = false;
@@ -53,8 +71,9 @@ export class CrossThreadStatusCollector {
53
71
 
54
72
  // Find the pending request by requestId
55
73
  const pendingResponses = this.awaitingResponses.get(message.requestId);
56
- if (pendingResponses) {
57
- pendingResponses.push({
74
+ if (pendingResponses && typeof message.threadId === 'number') {
75
+ pendingResponses.set(message.threadId, {
76
+ threadId: message.threadId,
58
77
  workerIndex: message.workerIndex,
59
78
  isMainThread: message.isMainThread || false,
60
79
  statuses: message.statuses || [],
@@ -115,15 +134,10 @@ export class CrossThreadStatusCollector {
115
134
 
116
135
  // Generate unique request ID and set up response collection
117
136
  const requestId = this.nextRequestId++;
118
- const responses: Array<WorkerComponentStatuses> = [];
137
+ const responses = new Map<number, WorkerComponentStatuses>();
119
138
  this.awaitingResponses.set(requestId, responses);
120
139
 
121
- // Calculate expected number of responses
122
- // Total threads = main thread (1) + worker threads (workerCount)
123
- const workerCount = getWorkerCount() || 1;
124
- const totalThreads = workerCount + 1;
125
- // We expect responses from all threads except ourselves
126
- const expectedResponses = totalThreads - 1;
140
+ const expectedThreadIds = getEligibleBroadcastRecipientThreadIds();
127
141
 
128
142
  // Set up response collection with timeout
129
143
  const responsePromise = new Promise<Array<WorkerComponentStatuses>>((resolve, reject) => {
@@ -132,26 +146,28 @@ export class CrossThreadStatusCollector {
132
146
  // Check if we've received all expected responses
133
147
  const checkComplete = () => {
134
148
  const collectedResponses = this.awaitingResponses.get(requestId);
135
- if (collectedResponses && collectedResponses.length >= expectedResponses && !resolved) {
136
- resolved = true;
137
- cleanup();
138
- logger.trace?.(`Collected all ${collectedResponses.length} expected responses for request ${requestId}`);
139
- resolve(collectedResponses);
149
+ if (!collectedResponses || resolved) return;
150
+ for (const threadId of expectedThreadIds) {
151
+ if (!collectedResponses.has(threadId)) return;
140
152
  }
153
+ resolved = true;
154
+ cleanup();
155
+ logger.trace?.(`Collected all ${collectedResponses.size} expected responses for request ${requestId}`);
156
+ resolve([...collectedResponses.values()]);
141
157
  };
142
158
 
143
159
  // Set up timeout as fallback
144
160
  const timeoutHandle = setTimeout(() => {
145
161
  if (!resolved) {
146
162
  resolved = true;
147
- const collectedResponses = this.awaitingResponses.get(requestId) || [];
163
+ const collectedResponses = this.awaitingResponses.get(requestId) || new Map();
148
164
  this.awaitingResponses.delete(requestId);
149
165
  // Log timeout with diagnostic info
150
166
  logger.debug?.(
151
- `Collection timeout for request ${requestId}: collected ${collectedResponses.length}/${expectedResponses} responses`
167
+ `Collection timeout for request ${requestId}: collected ${collectedResponses.size}/${expectedThreadIds.size} responses`
152
168
  );
153
169
  // Resolve with whatever we've collected so far
154
- resolve(collectedResponses);
170
+ resolve([...collectedResponses.values()]);
155
171
  }
156
172
  }, this.timeout);
157
173
 
@@ -196,20 +212,14 @@ export class CrossThreadStatusCollector {
196
212
  const localThreadLabel = localWorkerIndex === undefined ? 'main' : `worker-${localWorkerIndex}`;
197
213
 
198
214
  for (const [name, status] of localStatuses) {
199
- aggregatedStatuses.set(`${name}@${localThreadLabel}`, {
200
- ...status,
201
- workerIndex: localWorkerIndex,
202
- });
215
+ this.setThreadStatus(aggregatedStatuses, name, localThreadLabel, status, localWorkerIndex);
203
216
  }
204
217
 
205
218
  // Add responses from other threads
206
219
  for (const response of collectedResponses) {
220
+ const threadLabel = response.isMainThread ? 'main' : `worker-${response.workerIndex}`;
207
221
  for (const [name, status] of response.statuses) {
208
- const threadLabel = response.isMainThread ? 'main' : `worker-${response.workerIndex}`;
209
- aggregatedStatuses.set(`${name}@${threadLabel}`, {
210
- ...status,
211
- workerIndex: response.workerIndex,
212
- });
222
+ this.setThreadStatus(aggregatedStatuses, name, threadLabel, status, response.workerIndex, response.threadId);
213
223
  }
214
224
  }
215
225
 
@@ -232,6 +242,26 @@ export class CrossThreadStatusCollector {
232
242
  }
233
243
  }
234
244
 
245
+ private setThreadStatus(
246
+ statuses: Map<string, ComponentStatusSummary>,
247
+ name: string,
248
+ threadLabel: string,
249
+ status: ComponentStatusSummary,
250
+ workerIndex: number | undefined,
251
+ threadId?: number
252
+ ): void {
253
+ const baseKey = `${name}@${threadLabel}`;
254
+ let key = baseKey;
255
+ if (statuses.has(baseKey)) {
256
+ if (threadId === undefined) throw new Error(`Physical thread identity is required for duplicate ${baseKey}`);
257
+ key += `${THREAD_IDENTITY_SEPARATOR}${threadId}`;
258
+ }
259
+ statuses.set(key, {
260
+ ...status,
261
+ workerIndex,
262
+ });
263
+ }
264
+
235
265
  /**
236
266
  * Get status from local thread only (fallback when cross-thread collection fails)
237
267
  */
@@ -242,10 +272,7 @@ export class CrossThreadStatusCollector {
242
272
  const localThreadLabel = localWorkerIndex === undefined ? 'main' : `worker-${localWorkerIndex}`;
243
273
 
244
274
  for (const [name, status] of localStatuses) {
245
- fallbackStatuses.set(`${name}@${localThreadLabel}`, {
246
- ...status,
247
- workerIndex: localWorkerIndex,
248
- });
275
+ this.setThreadStatus(fallbackStatuses, name, localThreadLabel, status, localWorkerIndex);
249
276
  }
250
277
  return fallbackStatuses;
251
278
  }
@@ -318,7 +345,8 @@ export class StatusAggregator {
318
345
  // Analyze all instances of this component
319
346
  for (const [nameWithThread, status] of statusEntries) {
320
347
  const atIndex = nameWithThread.lastIndexOf('@');
321
- const threadLabel = atIndex !== -1 ? nameWithThread.substring(atIndex + 1) : '';
348
+ const threadLabelWithIdentity = atIndex !== -1 ? nameWithThread.substring(atIndex + 1) : '';
349
+ const threadLabel = stripThreadIdentity(threadLabelWithIdentity);
322
350
 
323
351
  // Convert lastChecked to ms since epoch
324
352
  const checkTime =
@@ -330,7 +358,7 @@ export class StatusAggregator {
330
358
  } else if (threadLabel && threadLabel.startsWith('worker-')) {
331
359
  const workerIndex = parseInt(threadLabel.substring(7)); // 'worker-'.length = 7
332
360
  if (!isNaN(workerIndex)) {
333
- lastCheckedTimes.workers[workerIndex] = checkTime;
361
+ lastCheckedTimes.workers[workerIndex] = Math.max(lastCheckedTimes.workers[workerIndex] ?? 0, checkTime);
334
362
  }
335
363
  }
336
364
 
@@ -360,7 +388,12 @@ export class StatusAggregator {
360
388
  // There are inconsistencies - populate abnormalities
361
389
  for (const [nameWithThread, status] of statusEntries) {
362
390
  if (status.status !== determinedStatus) {
363
- abnormalities.set(nameWithThread, {
391
+ const publicNameWithThread = stripThreadIdentity(nameWithThread);
392
+ const existing = abnormalities.get(publicNameWithThread);
393
+ if (existing && STATUS_PRIORITY.indexOf(existing.status) <= STATUS_PRIORITY.indexOf(status.status)) {
394
+ continue;
395
+ }
396
+ abnormalities.set(publicNameWithThread, {
364
397
  workerIndex: status.workerIndex !== undefined ? status.workerIndex : -1,
365
398
  status: status.status,
366
399
  message: status.message,
@@ -391,15 +424,7 @@ export class StatusAggregator {
391
424
  * Determine overall status based on priority
392
425
  */
393
426
  private static determineOverallStatus(statusCounts: Map<ComponentStatusLevel, number>): ComponentStatusLevel {
394
- const statusPriority = [
395
- COMPONENT_STATUS_LEVELS.ERROR,
396
- COMPONENT_STATUS_LEVELS.WARNING,
397
- COMPONENT_STATUS_LEVELS.LOADING,
398
- COMPONENT_STATUS_LEVELS.UNKNOWN,
399
- COMPONENT_STATUS_LEVELS.HEALTHY,
400
- ];
401
-
402
- for (const priorityStatus of statusPriority) {
427
+ for (const priorityStatus of STATUS_PRIORITY) {
403
428
  if (statusCounts.has(priorityStatus) && statusCounts.get(priorityStatus)! > 0) {
404
429
  return priorityStatus;
405
430
  }
@@ -75,6 +75,7 @@ export interface AggregatedComponentStatus {
75
75
  * Component status information from a worker thread
76
76
  */
77
77
  export interface WorkerComponentStatuses {
78
+ threadId?: number;
78
79
  workerIndex: number | undefined;
79
80
  isMainThread: boolean;
80
81
  statuses: Array<[string, ComponentStatusSummary]>;
@@ -51,6 +51,7 @@ const envMgr = __importStar(require("../utility/environment/environmentManager.j
51
51
  const path = __importStar(require("node:path"));
52
52
  const node_fs_1 = require("node:fs");
53
53
  const nodeName_ts_1 = require("../server/nodeName.js");
54
+ const restartExitWatchdog_ts_1 = require("./restartExitWatchdog.js");
54
55
  envMgr.initSync();
55
56
  const RESTART_RESPONSE = `Restarting Harper. This may take up to ${hdbTerms.RESTART_TIMEOUT_MS / 1000} seconds.`;
56
57
  const INVALID_SERVICE_ERR = 'Invalid service';
@@ -86,30 +87,47 @@ async function restart(req) {
86
87
  }
87
88
  if (worker_threads_1.isMainThread) {
88
89
  harper_logger_ts_1.default.notify(RESTART_RESPONSE);
89
- if (envMgr.get(hdbTerms.CONFIG_PARAMS.STORAGE_COMPACTONSTART))
90
+ if (envMgr.get(hdbTerms.CONFIG_PARAMS.STORAGE_COMPACTONSTART)) {
91
+ harper_logger_ts_1.default.info('Compacting storage before restart; the restart timeout begins after compaction completes');
90
92
  await (0, copyDb_ts_1.compactOnStart)();
93
+ }
91
94
  setTimeout(async () => {
92
- // It seems like you should just be able to start the other process and kill this process and everything should
93
- // be cleaned up, however that doesn't work for some reason; the socket listening fds somehow get transferred to the
94
- // child process if they are not explicitly closed. And when transferred they are orphaned listening, accepting
95
- // connections and hanging. So we need to explicitly close down all the workers and then start the new process
96
- // and shut down.
97
- harper_logger_ts_1.default.debug('Shutdown workers');
98
- await (0, manageThreads_js_1.shutdownWorkersNow)();
99
- const { closeServers } = require('../server/threads/threadServer.js');
100
- await closeServers();
101
- await processMan.cleanupChildrenProcesses(false);
102
- // remove pid file so it doesn't trip up the launch
103
- (0, node_fs_1.unlinkSync)(path.join(envMgr.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), hdbTerms.HDB_PID_FILE));
104
- harper_logger_ts_1.default.debug('Starting new process...');
105
- if (process.env.HARPER_EXIT_ON_RESTART) {
106
- // use this to exit the process so that it will be restarted by the
107
- // PM/container/orchestrator.
108
- harper_logger_ts_1.default.warn('Exiting Harper process to trigger a container restart');
109
- process.exit(0);
95
+ try {
96
+ // Latch before the watchdog handshake, not at shutdownWorkersNow() below: a component's
97
+ // debounced requestRestart() landing in between would otherwise reload root components and
98
+ // pre-start an HTTP replacement into a process that is already exiting.
99
+ (0, manageThreads_js_1.beginProcessShutdown)();
100
+ // Off Linux the watchdog can never arm, and armRestartExitWatchdog() has already warned.
101
+ if (process.env.HARPER_EXIT_ON_RESTART &&
102
+ !(await (0, restartExitWatchdog_ts_1.armRestartExitWatchdog)(hdbTerms.RESTART_TIMEOUT_MS)) &&
103
+ process.platform === 'linux')
104
+ harper_logger_ts_1.default.error('Restart exit watchdog is unavailable; restart teardown is unbounded');
105
+ // It seems like you should just be able to start the other process and kill this process and everything should
106
+ // be cleaned up, however that doesn't work for some reason; the socket listening fds somehow get transferred to the
107
+ // child process if they are not explicitly closed. And when transferred they are orphaned listening, accepting
108
+ // connections and hanging. So we need to explicitly close down all the workers and then start the new process
109
+ // and shut down.
110
+ harper_logger_ts_1.default.debug('Shutdown workers');
111
+ await (0, manageThreads_js_1.shutdownWorkersNow)();
112
+ const { closeServers } = require('../server/threads/threadServer.js');
113
+ await closeServers();
114
+ await processMan.cleanupChildrenProcesses(false);
115
+ // remove pid file so it doesn't trip up the launch
116
+ (0, node_fs_1.rmSync)(path.join(envMgr.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), hdbTerms.HDB_PID_FILE), { force: true });
117
+ harper_logger_ts_1.default.debug('Starting new process...');
118
+ if (process.env.HARPER_EXIT_ON_RESTART) {
119
+ // use this to exit the process so that it will be restarted by the
120
+ // PM/container/orchestrator.
121
+ harper_logger_ts_1.default.warn('Exiting Harper process to trigger a container restart');
122
+ process.exit(0);
123
+ }
124
+ // now launch the new process and exit this process
125
+ await require('./run').launch(true);
126
+ }
127
+ catch (error) {
128
+ harper_logger_ts_1.default.fatal('Restart teardown failed; exiting Harper', error);
129
+ process.exit(1);
110
130
  }
111
- // now launch the new process and exit this process
112
- require('./run').launch(true);
113
131
  }, 50); // can't await this because it is going to do an exit(), but wait for 50ms so we give the HTTP thread a
114
132
  // chance to return a response
115
133
  }
@@ -1 +1 @@
1
- {"version":3,"file":"restart.js","sourceRoot":"","sources":["../../bin/restart.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBJ,0BAAO;AAAE,wCAAc;AApBhC,wDAAgC;AAChC,mDAA0D;AAC1D,iEAAmD;AACnD,2FAA4D;AAC5D,8FAAgF;AAChF,2CAA6C;AAC7C,yEAAyG;AACzG,+DAA0E;AAC1E,MAAM,EAAE,iBAAiB,EAAE,GAAG,uBAAS,CAAC;AACxC,qFAAuE;AACvE,gDAAkC;AAClC,qCAAqC;AACrC,uDAAwD;AACxD,MAAM,CAAC,QAAQ,EAAE,CAAC;AAElB,MAAM,gBAAgB,GAAG,0CAA0C,QAAQ,CAAC,kBAAkB,GAAG,IAAI,WAAW,CAAC;AACjH,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAE9C,IAAI,aAAa,CAAC;AAIlB,uGAAuG;AACvG,IAAI,6BAAY,EAAE,CAAC;IAClB,IAAA,kCAAe,EAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACzE,IAAI,OAAO,CAAC,UAAU;YAAE,MAAM,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;;YACzE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,OAAO,CAAC,GAAQ;IAC9B,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,IAAA,kBAAQ,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO;IACR,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QACtC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACtE,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,gBAAgB,CAAC;IACzB,CAAC;IAED,IAAI,6BAAY,EAAE,CAAC;QAClB,0BAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEnC,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC;YAAE,MAAM,IAAA,0BAAc,GAAE,CAAC;QAEtF,UAAU,CAAC,KAAK,IAAI,EAAE;YACrB,+GAA+G;YAC/G,oHAAoH;YACpH,+GAA+G;YAC/G,8GAA8G;YAC9G,iBAAiB;YACjB,0BAAS,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YACpC,MAAM,IAAA,qCAAkB,GAAE,CAAC;YAC3B,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,mCAAmC,CAAC,CAAC;YACtE,MAAM,YAAY,EAAE,CAAC;YACrB,MAAM,UAAU,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC;YACjD,mDAAmD;YACnD,IAAA,oBAAU,EAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;YAC1F,0BAAS,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;gBACxC,mEAAmE;gBACnE,6BAA6B;gBAC7B,0BAAS,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;gBACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjB,CAAC;YACD,mDAAmD;YACnD,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,uGAAuG;QAC/G,8BAA8B;IAC/B,CAAC;SAAM,CAAC;QACP,+FAA+F;QAC/F,2BAAU,CAAC,WAAW,CAAC;YACtB,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,OAAO;SACtC,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,gBAAgB,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,cAAc,CAAC,GAAQ;IACrC,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC;IACtB,IAAI,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;QAC1D,MAAM,IAAA,4BAAc,EAAC,IAAI,KAAK,EAAE,EAAE,mBAAmB,EAAE,iBAAiB,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACnH,CAAC;IACD,UAAU,CAAC,yBAAyB,EAAE,CAAC;IACvC,IAAI,CAAC,6BAAY,EAAE,CAAC;QACnB,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,MAAc,CAAC,MAAM,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,+CAA+C;QACrG,CAAC;QACD,2BAAU,CAAC,WAAW,CAAC;YACtB,IAAI,EAAE,QAAQ,CAAC,eAAe,CAAC,OAAO;YACtC,UAAU,EAAE,OAAO;SACnB,CAAC,CAAC;QACH,2BAAU,CAAC,GAAG,EAAE,CAAC,CAAC,oDAAoD;QACtE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,2BAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;gBAChC,IAAI,GAAG,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBACrC,OAAO,EAAE,CAAC;oBACV,2BAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,mBAAmB,CAAC;QACxB,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACpB,GAAG,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,8DAA8D;YACtF,mBAAmB,GAAG,EAAE,CAAC;YACzB,KAAK,IAAI,IAAI,IAAK,MAAc,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,IAAA,6BAAe,GAAE;oBAAE,SAAS;gBAC9C,8BAA8B;gBAC9B,IAAI,MAAM,CAAC;gBACX,IAAI,CAAC;oBACJ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAO,MAAc,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;gBACxF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,wFAAwF;oBACxF,mBAAmB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;oBACpE,SAAS;gBACV,CAAC;gBACD,sEAAsE;gBACtE,mBAAmB,CAAC,IAAI,CACvB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACrC,MAAM,cAAc,GAAG,GAAG,CAAC;oBAC3B,IAAI,WAAW,GAAG,IAAI,CAAC,CAAC,aAAa;oBACrC,IAAI,QAAQ,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;wBACrC,IAAI,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC;4BACxB,aAAa,CAAC,QAAQ,CAAC,CAAC;4BACxB,IAAI,KAAK,GAAQ,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;4BAC5E,KAAK,CAAC,UAAU,GAAG,mBAAmB,CAAC,CAAC,+BAA+B;4BACvE,MAAM,CAAC,KAAK,CAAC,CAAC;wBACf,CAAC;wBACD,IAAI,QAAQ,GAAG,MAAO,MAAc,CAAC,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE;4BACjF,SAAS,EAAE,SAAS;4BACpB,EAAE,EAAE,MAAM;yBACV,CAAC,CAAC;wBACH,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;wBACtC,IAAI,SAAS,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;4BACrC,aAAa,CAAC,QAAQ,CAAC,CAAC;4BACxB,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;wBAC1D,CAAC;wBACD,IAAI,SAAS,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;4BAClC,aAAa,CAAC,QAAQ,CAAC,CAAC;4BACxB,IAAI,KAAK,GAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;4BAC9C,KAAK,CAAC,UAAU,GAAG,mBAAmB,CAAC,CAAC,+BAA+B;4BACvE,MAAM,CAAC,KAAK,CAAC,CAAC;wBACf,CAAC;oBACF,CAAC,EAAE,cAAc,CAAC,CAAC;gBACpB,CAAC,CAAC,CACF,CAAC;YACH,CAAC;YACD,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC;QAC5C,CAAC;QACD,OAAO;IACR,CAAC;IAED,IAAI,MAAM,CAAC;IACX,QAAQ,OAAO,EAAE,CAAC;QACjB,KAAK,kBAAkB,CAAC;QACxB,KAAK,kBAAkB,CAAC;QACxB,KAAK,QAAQ,CAAC,oBAAoB,CAAC,QAAQ,CAAC;QAC5C,KAAK,QAAQ,CAAC,oBAAoB,CAAC,YAAY,CAAC;QAChD,KAAK,QAAQ,CAAC,oBAAoB,CAAC,IAAI;YACtC,IAAI,aAAa;gBAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACzD,0BAAS,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;YAE5C,IAAI,aAAa,EAAE,CAAC;gBACnB,MAAM,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC5D,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAA,iCAAc,EAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;YACD,MAAM;QACP;YACC,MAAM,GAAG,yBAAyB,OAAO,EAAE,CAAC;YAC5C,MAAM;IACR,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACZ,0BAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,aAAa;YAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC;IACf,CAAC;IACD,IAAI,OAAO,KAAK,kBAAkB;QAAE,OAAO,GAAG,kBAAkB,CAAC;IACjE,OAAO,cAAc,OAAO,EAAE,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"restart.js","sourceRoot":"","sources":["../../bin/restart.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export declare const WATCHDOG_READY_TOKEN = "harper-restart-exit-watchdog-ready";
3
+ export declare const WATCHDOG_SCRIPT = "\nparent_pid=$1\ndelay_seconds=$2\n[ \"$parent_pid\" -gt 1 ] || exit 0\ncommand -v sleep > /dev/null 2>&1 || exit 0\n# Shell PPID variables are snapshots; procfs reflects reparenting after Harper exits.\nread -r _ _ _ current_parent _ < \"/proc/$$/stat\" || exit 0\n[ \"$current_parent\" = \"$parent_pid\" ] || exit 0\necho \"harper-restart-exit-watchdog-ready\"\nsleep \"$delay_seconds\" || exit 0\nread -r _ _ _ current_parent _ < \"/proc/$$/stat\" || exit 0\n[ \"$current_parent\" = \"$parent_pid\" ] || exit 0\necho \"harper: restart exit watchdog force-killing pid $parent_pid after $delay_seconds seconds\" >&2\nkill -KILL \"$parent_pid\"\n";
4
+ export declare function armRestartExitWatchdog(timeoutMs: number): Promise<boolean>;
5
+ export declare function waitForWatchdogReady(watchdog: ChildProcess, timeoutMs?: number): Promise<boolean>;