@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.
- package/bin/restart.ts +48 -23
- package/bin/restartExitWatchdog.ts +99 -0
- package/bin/run.ts +16 -10
- package/components/status/crossThread.ts +69 -44
- package/components/status/types.ts +1 -0
- package/dist/bin/restart.js +39 -21
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/restartExitWatchdog.d.ts +5 -0
- package/dist/bin/restartExitWatchdog.js +100 -0
- package/dist/bin/restartExitWatchdog.js.map +1 -0
- package/dist/bin/run.d.ts +2 -0
- package/dist/bin/run.js +17 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/components/status/crossThread.d.ts +1 -0
- package/dist/components/status/crossThread.js +57 -41
- package/dist/components/status/crossThread.js.map +1 -1
- package/dist/components/status/types.d.ts +1 -0
- package/dist/resources/DatabaseTransaction.d.ts +46 -1
- package/dist/resources/DatabaseTransaction.js +173 -9
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -0
- package/dist/resources/LMDBTransaction.js +7 -1
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/Resource.js +5 -4
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +20 -0
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Table.js +56 -9
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +11 -0
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +153 -51
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/transaction.js +4 -2
- package/dist/resources/transaction.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +2 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +2 -0
- package/dist/server/threads/manageThreads.js +37 -5
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/utility/processManagement/processManagement.js +21 -3
- package/dist/utility/processManagement/processManagement.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/resources/DESIGN.md +19 -18
- package/resources/DatabaseTransaction.ts +187 -17
- package/resources/LMDBTransaction.ts +7 -1
- package/resources/Resource.ts +6 -5
- package/resources/ResourceInterface.ts +20 -0
- package/resources/Table.ts +60 -8
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +153 -54
- package/resources/transaction.ts +5 -3
- package/schema.graphql +8 -3
- package/server/DESIGN.md +6 -0
- package/server/itc/serverHandlers.js +2 -1
- package/server/threads/manageThreads.js +34 -5
- package/studio/web/assets/{Chat-BvOlYIhe.js → Chat-BNHY84-Z.js} +1 -1
- package/studio/web/assets/{FloatingChat-fCJiJCH2.js → FloatingChat-BbztUci0.js} +3 -3
- package/studio/web/assets/{apiToken-bKwPv0uN.js → apiToken-D0x_S8Wq.js} +1 -1
- package/studio/web/assets/{applications-BZ47njJ_.js → applications-BU83fSEG.js} +1 -1
- package/studio/web/assets/{index-DF1fSnXV.js → index-BQB9R8Ug.js} +4 -4
- package/studio/web/assets/{index.lazy-DWt8ubrr.js → index.lazy-CtErm5q7.js} +3 -3
- package/studio/web/assets/{notifications-B5ZBOni1.js → notifications-B8rDagJM.js} +1 -1
- package/studio/web/assets/{notifications-gVx0lA8H.js → notifications-CPwYKDDH.js} +1 -1
- package/studio/web/assets/{profile-D7g0x28A.js → profile-D0hB1xdm.js} +1 -1
- package/studio/web/assets/{regions-DRkhlkiD.js → regions-y_jxsBpN.js} +1 -1
- package/studio/web/assets/{setComponentFile-DQkkobG5.js → setComponentFile-DSHbURTI.js} +1 -1
- package/studio/web/assets/{setup-nf-EOheW.js → setup-J5SrM1Kp.js} +1 -1
- package/studio/web/assets/{status-B2tOM3RF.js → status-IoJkDzlx.js} +1 -1
- package/studio/web/assets/{swagger-ui-react-UktCouQ0.js → swagger-ui-react-BlgSS25O.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-alqAn3dT.js → useEntityRestURL-qfahJnvr.js} +1 -1
- package/studio/web/index.html +1 -1
- 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 {
|
|
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 {
|
|
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))
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
66
|
+
const exit = () => {
|
|
67
|
+
beginProcessShutdown();
|
|
61
68
|
removeHdbPid();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 {
|
|
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,
|
|
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.
|
|
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
|
|
137
|
+
const responses = new Map<number, WorkerComponentStatuses>();
|
|
119
138
|
this.awaitingResponses.set(requestId, responses);
|
|
120
139
|
|
|
121
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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]>;
|
package/dist/bin/restart.js
CHANGED
|
@@ -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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
}
|
package/dist/bin/restart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"restart.js","sourceRoot":"","sources":["../../bin/restart.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"restart.js","sourceRoot":"","sources":["../../bin/restart.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BJ,0BAAO;AAAE,wCAAc;AA1BhC,wDAAgC;AAChC,mDAA0D;AAC1D,iEAAmD;AACnD,2FAA4D;AAC5D,8FAAgF;AAChF,2CAA6C;AAC7C,yEAK4C;AAC5C,+DAA0E;AAC1E,MAAM,EAAE,iBAAiB,EAAE,GAAG,uBAAS,CAAC;AACxC,qFAAuE;AACvE,gDAAkC;AAClC,qCAAiC;AACjC,uDAAwD;AACxD,qEAAkE;AAClE,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,EAAE,CAAC;YAC/D,0BAAS,CAAC,IAAI,CAAC,0FAA0F,CAAC,CAAC;YAC3G,MAAM,IAAA,0BAAc,GAAE,CAAC;QACxB,CAAC;QAED,UAAU,CAAC,KAAK,IAAI,EAAE;YACrB,IAAI,CAAC;gBACJ,wFAAwF;gBACxF,2FAA2F;gBAC3F,wEAAwE;gBACxE,IAAA,uCAAoB,GAAE,CAAC;gBACvB,yFAAyF;gBACzF,IACC,OAAO,CAAC,GAAG,CAAC,sBAAsB;oBAClC,CAAC,CAAC,MAAM,IAAA,+CAAsB,EAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;oBAC5D,OAAO,CAAC,QAAQ,KAAK,OAAO;oBAE5B,0BAAS,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAC;gBACxF,+GAA+G;gBAC/G,oHAAoH;gBACpH,+GAA+G;gBAC/G,8GAA8G;gBAC9G,iBAAiB;gBACjB,0BAAS,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBACpC,MAAM,IAAA,qCAAkB,GAAE,CAAC;gBAC3B,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,mCAAmC,CAAC,CAAC;gBACtE,MAAM,YAAY,EAAE,CAAC;gBACrB,MAAM,UAAU,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC;gBACjD,mDAAmD;gBACnD,IAAA,gBAAM,EAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvG,0BAAS,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;gBAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;oBACxC,mEAAmE;oBACnE,6BAA6B;oBAC7B,0BAAS,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;oBACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACjB,CAAC;gBACD,mDAAmD;gBACnD,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,0BAAS,CAAC,KAAK,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;gBAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjB,CAAC;QACF,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"}
|
|
@@ -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>;
|