@zintrust/core 1.5.4 → 1.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/z.js +74 -7
- package/bin/zin.js +164 -7
- package/bin/zintrust.d.ts.map +1 -1
- package/bin/zintrust.js +74 -7
- package/bin/zt.js +81 -4
- package/package.json +1 -1
- package/src/boot/bootstrap.d.ts.map +1 -1
- package/src/boot/bootstrap.js +105 -40
- package/src/boot/registry/runtime.d.ts.map +1 -1
- package/src/boot/registry/runtime.js +37 -2
- package/src/boot/registry/worker.d.ts.map +1 -1
- package/src/boot/registry/worker.js +3 -2
- package/src/cli/utils/spawn.d.ts.map +1 -1
- package/src/cli/utils/spawn.js +255 -46
- package/src/helper/ShutdownTrace.d.ts +9 -0
- package/src/helper/ShutdownTrace.d.ts.map +1 -0
- package/src/helper/ShutdownTrace.js +165 -0
- package/src/helper/index.d.ts +1 -0
- package/src/helper/index.d.ts.map +1 -1
- package/src/helper/index.js +1 -0
- package/src/index.js +3 -3
- package/src/tools/queue/QueueReliabilityOrchestrator.d.ts.map +1 -1
- package/src/tools/queue/QueueReliabilityOrchestrator.js +11 -0
package/bin/z.js
CHANGED
|
@@ -18,23 +18,90 @@ const nodeArgs = target.endsWith('.ts')
|
|
|
18
18
|
? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
|
|
19
19
|
: [target, ...process.argv.slice(2)];
|
|
20
20
|
const child = spawn(process.execPath, nodeArgs, {
|
|
21
|
-
stdio: 'inherit',
|
|
21
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
22
22
|
env: process.env,
|
|
23
23
|
});
|
|
24
|
+
child.stdout?.on('data', (chunk) => {
|
|
25
|
+
process.stdout.write(chunk);
|
|
26
|
+
});
|
|
27
|
+
child.stderr?.on('data', (chunk) => {
|
|
28
|
+
process.stderr.write(chunk);
|
|
29
|
+
});
|
|
30
|
+
let childClosed = false;
|
|
31
|
+
let delayedSignalTimer;
|
|
32
|
+
const clearDelayedSignal = () => {
|
|
33
|
+
if (delayedSignalTimer === undefined)
|
|
34
|
+
return;
|
|
35
|
+
clearTimeout(delayedSignalTimer);
|
|
36
|
+
delayedSignalTimer = undefined;
|
|
37
|
+
};
|
|
38
|
+
const forwardSignal = (signal) => {
|
|
39
|
+
if (childClosed)
|
|
40
|
+
return;
|
|
41
|
+
try {
|
|
42
|
+
child.kill(signal);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// best-effort
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const scheduleSignalForward = (signal) => {
|
|
49
|
+
if (childClosed || delayedSignalTimer !== undefined)
|
|
50
|
+
return;
|
|
51
|
+
delayedSignalTimer = globalThis.setTimeout(() => {
|
|
52
|
+
delayedSignalTimer = undefined;
|
|
53
|
+
forwardSignal(signal);
|
|
54
|
+
}, 1500);
|
|
55
|
+
delayedSignalTimer.unref?.();
|
|
56
|
+
};
|
|
57
|
+
const onSigint = () => {
|
|
58
|
+
if (process.stdin.isTTY === true) {
|
|
59
|
+
scheduleSignalForward('SIGINT');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
forwardSignal('SIGINT');
|
|
63
|
+
};
|
|
64
|
+
const onSigterm = () => {
|
|
65
|
+
if (process.stdin.isTTY === true) {
|
|
66
|
+
scheduleSignalForward('SIGTERM');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
forwardSignal('SIGTERM');
|
|
70
|
+
};
|
|
71
|
+
process.on('SIGINT', onSigint);
|
|
72
|
+
process.on('SIGTERM', onSigterm);
|
|
24
73
|
const result = await new Promise((resolve, reject) => {
|
|
25
74
|
let settled = false;
|
|
26
|
-
|
|
75
|
+
let childResult = {
|
|
76
|
+
exitCode: null,
|
|
77
|
+
signal: null,
|
|
78
|
+
};
|
|
79
|
+
const finalize = () => {
|
|
27
80
|
if (settled) {
|
|
28
81
|
return;
|
|
29
82
|
}
|
|
30
83
|
settled = true;
|
|
84
|
+
childClosed = true;
|
|
85
|
+
clearDelayedSignal();
|
|
86
|
+
process.off('SIGINT', onSigint);
|
|
87
|
+
process.off('SIGTERM', onSigterm);
|
|
31
88
|
child.off?.('error', reject);
|
|
32
|
-
child.off?.('exit',
|
|
33
|
-
child.off?.('close',
|
|
34
|
-
resolve(
|
|
89
|
+
child.off?.('exit', handleExit);
|
|
90
|
+
child.off?.('close', handleClose);
|
|
91
|
+
resolve(childResult);
|
|
92
|
+
};
|
|
93
|
+
const handleExit = (exitCode, signal) => {
|
|
94
|
+
childResult = { exitCode, signal };
|
|
95
|
+
};
|
|
96
|
+
const handleClose = (exitCode, signal) => {
|
|
97
|
+
childResult = {
|
|
98
|
+
exitCode: childResult.exitCode ?? exitCode,
|
|
99
|
+
signal: childResult.signal ?? signal,
|
|
100
|
+
};
|
|
101
|
+
finalize();
|
|
35
102
|
};
|
|
36
103
|
child.once('error', reject);
|
|
37
|
-
child.once('exit',
|
|
38
|
-
child.once('close',
|
|
104
|
+
child.once('exit', handleExit);
|
|
105
|
+
child.once('close', handleClose);
|
|
39
106
|
});
|
|
40
107
|
process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
|
package/bin/zin.js
CHANGED
|
@@ -8,6 +8,21 @@ import { existsSync } from 'node:fs';
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
|
+
const CLI_SPAWN_TRACE_ENV_KEYS = ['CLI_SPAWN_TRACE', 'ZIN_SPAWN_TRACE'];
|
|
12
|
+
const isCliSpawnTraceEnabled = () => {
|
|
13
|
+
return CLI_SPAWN_TRACE_ENV_KEYS.some((key) => {
|
|
14
|
+
const raw = process.env[key];
|
|
15
|
+
if (typeof raw !== 'string')
|
|
16
|
+
return false;
|
|
17
|
+
const normalized = raw.trim().toLowerCase();
|
|
18
|
+
return (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on');
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const writeCliSpawnTrace = (label, details = {}) => {
|
|
22
|
+
if (!isCliSpawnTraceEnabled())
|
|
23
|
+
return;
|
|
24
|
+
process.stderr.write(`${JSON.stringify({ trace: 'cli-wrapper', label, pid: process.pid, details })}\n`);
|
|
25
|
+
};
|
|
11
26
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
12
27
|
const require = createRequire(import.meta.url);
|
|
13
28
|
const tsTarget = path.join(here, 'zintrust-main.ts');
|
|
@@ -18,23 +33,165 @@ const nodeArgs = target.endsWith('.ts')
|
|
|
18
33
|
? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
|
|
19
34
|
: [target, ...process.argv.slice(2)];
|
|
20
35
|
const child = spawn(process.execPath, nodeArgs, {
|
|
21
|
-
stdio: 'inherit',
|
|
36
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
22
37
|
env: process.env,
|
|
23
38
|
});
|
|
39
|
+
writeCliSpawnTrace('wrapper.child.started', {
|
|
40
|
+
childPid: child.pid,
|
|
41
|
+
command: process.execPath,
|
|
42
|
+
args: nodeArgs,
|
|
43
|
+
});
|
|
44
|
+
child.stdout?.on('data', (chunk) => {
|
|
45
|
+
writeCliSpawnTrace('wrapper.child.stdout.data', {
|
|
46
|
+
childPid: child.pid,
|
|
47
|
+
bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
|
|
48
|
+
});
|
|
49
|
+
process.stdout.write(chunk);
|
|
50
|
+
});
|
|
51
|
+
child.stdout?.on('end', () => {
|
|
52
|
+
writeCliSpawnTrace('wrapper.child.stdout.end', {
|
|
53
|
+
childPid: child.pid,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
child.stdout?.on('close', () => {
|
|
57
|
+
writeCliSpawnTrace('wrapper.child.stdout.close', {
|
|
58
|
+
childPid: child.pid,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
child.stderr?.on('data', (chunk) => {
|
|
62
|
+
writeCliSpawnTrace('wrapper.child.stderr.data', {
|
|
63
|
+
childPid: child.pid,
|
|
64
|
+
bytes: typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length,
|
|
65
|
+
});
|
|
66
|
+
process.stderr.write(chunk);
|
|
67
|
+
});
|
|
68
|
+
child.stderr?.on('end', () => {
|
|
69
|
+
writeCliSpawnTrace('wrapper.child.stderr.end', {
|
|
70
|
+
childPid: child.pid,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
child.stderr?.on('close', () => {
|
|
74
|
+
writeCliSpawnTrace('wrapper.child.stderr.close', {
|
|
75
|
+
childPid: child.pid,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
let childClosed = false;
|
|
79
|
+
let delayedSignalTimer;
|
|
80
|
+
const clearDelayedSignal = () => {
|
|
81
|
+
if (delayedSignalTimer === undefined)
|
|
82
|
+
return;
|
|
83
|
+
clearTimeout(delayedSignalTimer);
|
|
84
|
+
delayedSignalTimer = undefined;
|
|
85
|
+
};
|
|
86
|
+
const forwardSignal = (signal) => {
|
|
87
|
+
if (childClosed)
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
writeCliSpawnTrace('wrapper.signal.forward.attempt', {
|
|
91
|
+
childPid: child.pid,
|
|
92
|
+
signal,
|
|
93
|
+
});
|
|
94
|
+
child.kill(signal);
|
|
95
|
+
writeCliSpawnTrace('wrapper.signal.forward.complete', {
|
|
96
|
+
childPid: child.pid,
|
|
97
|
+
signal,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// best-effort
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const scheduleSignalForward = (signal) => {
|
|
105
|
+
if (childClosed || delayedSignalTimer !== undefined)
|
|
106
|
+
return;
|
|
107
|
+
delayedSignalTimer = globalThis.setTimeout(() => {
|
|
108
|
+
delayedSignalTimer = undefined;
|
|
109
|
+
writeCliSpawnTrace('wrapper.signal.delay.fire', {
|
|
110
|
+
childPid: child.pid,
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
forwardSignal(signal);
|
|
114
|
+
}, 1500);
|
|
115
|
+
writeCliSpawnTrace('wrapper.signal.delay.schedule', {
|
|
116
|
+
childPid: child.pid,
|
|
117
|
+
signal,
|
|
118
|
+
delayMs: 1500,
|
|
119
|
+
});
|
|
120
|
+
delayedSignalTimer.unref?.();
|
|
121
|
+
};
|
|
122
|
+
const onSigint = () => {
|
|
123
|
+
writeCliSpawnTrace('wrapper.signal.received', {
|
|
124
|
+
childPid: child.pid,
|
|
125
|
+
signal: 'SIGINT',
|
|
126
|
+
tty: process.stdin.isTTY === true,
|
|
127
|
+
});
|
|
128
|
+
if (process.stdin.isTTY === true) {
|
|
129
|
+
scheduleSignalForward('SIGINT');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
forwardSignal('SIGINT');
|
|
133
|
+
};
|
|
134
|
+
const onSigterm = () => {
|
|
135
|
+
writeCliSpawnTrace('wrapper.signal.received', {
|
|
136
|
+
childPid: child.pid,
|
|
137
|
+
signal: 'SIGTERM',
|
|
138
|
+
tty: process.stdin.isTTY === true,
|
|
139
|
+
});
|
|
140
|
+
if (process.stdin.isTTY === true) {
|
|
141
|
+
scheduleSignalForward('SIGTERM');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
forwardSignal('SIGTERM');
|
|
145
|
+
};
|
|
146
|
+
process.on('SIGINT', onSigint);
|
|
147
|
+
process.on('SIGTERM', onSigterm);
|
|
24
148
|
const result = await new Promise((resolve, reject) => {
|
|
25
149
|
let settled = false;
|
|
26
|
-
|
|
150
|
+
let childResult = {
|
|
151
|
+
exitCode: null,
|
|
152
|
+
signal: null,
|
|
153
|
+
};
|
|
154
|
+
const finalize = () => {
|
|
27
155
|
if (settled) {
|
|
28
156
|
return;
|
|
29
157
|
}
|
|
30
158
|
settled = true;
|
|
159
|
+
writeCliSpawnTrace('wrapper.child.finalize', {
|
|
160
|
+
childPid: child.pid,
|
|
161
|
+
exitCode: childResult.exitCode,
|
|
162
|
+
signal: childResult.signal,
|
|
163
|
+
});
|
|
164
|
+
childClosed = true;
|
|
165
|
+
clearDelayedSignal();
|
|
166
|
+
process.off('SIGINT', onSigint);
|
|
167
|
+
process.off('SIGTERM', onSigterm);
|
|
31
168
|
child.off?.('error', reject);
|
|
32
|
-
child.off?.('exit',
|
|
33
|
-
child.off?.('close',
|
|
34
|
-
resolve(
|
|
169
|
+
child.off?.('exit', handleExit);
|
|
170
|
+
child.off?.('close', handleClose);
|
|
171
|
+
resolve(childResult);
|
|
172
|
+
};
|
|
173
|
+
const handleExit = (exitCode, signal) => {
|
|
174
|
+
writeCliSpawnTrace('wrapper.child.exit', {
|
|
175
|
+
childPid: child.pid,
|
|
176
|
+
exitCode,
|
|
177
|
+
signal,
|
|
178
|
+
});
|
|
179
|
+
childResult = { exitCode, signal };
|
|
180
|
+
};
|
|
181
|
+
const handleClose = (exitCode, signal) => {
|
|
182
|
+
writeCliSpawnTrace('wrapper.child.close', {
|
|
183
|
+
childPid: child.pid,
|
|
184
|
+
exitCode,
|
|
185
|
+
signal,
|
|
186
|
+
});
|
|
187
|
+
childResult = {
|
|
188
|
+
exitCode: childResult.exitCode ?? exitCode,
|
|
189
|
+
signal: childResult.signal ?? signal,
|
|
190
|
+
};
|
|
191
|
+
finalize();
|
|
35
192
|
};
|
|
36
193
|
child.once('error', reject);
|
|
37
|
-
child.once('exit',
|
|
38
|
-
child.once('close',
|
|
194
|
+
child.once('exit', handleExit);
|
|
195
|
+
child.once('close', handleClose);
|
|
39
196
|
});
|
|
40
197
|
process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
|
package/bin/zintrust.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zintrust.d.ts","sourceRoot":"","sources":["../../bin/zintrust.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"zintrust.d.ts","sourceRoot":"","sources":["../../bin/zintrust.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG;AA6HH,OAAO,EAAE,CAAC"}
|
package/bin/zintrust.js
CHANGED
|
@@ -21,23 +21,90 @@ const nodeArgs = target.endsWith('.ts')
|
|
|
21
21
|
? ['--import', tsxImportPath, target, ...process.argv.slice(2)]
|
|
22
22
|
: [target, ...process.argv.slice(2)];
|
|
23
23
|
const child = spawn(process.execPath, nodeArgs, {
|
|
24
|
-
stdio: 'inherit',
|
|
24
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
25
25
|
env: process.env,
|
|
26
26
|
});
|
|
27
|
+
child.stdout?.on('data', (chunk) => {
|
|
28
|
+
process.stdout.write(chunk);
|
|
29
|
+
});
|
|
30
|
+
child.stderr?.on('data', (chunk) => {
|
|
31
|
+
process.stderr.write(chunk);
|
|
32
|
+
});
|
|
33
|
+
let childClosed = false;
|
|
34
|
+
let delayedSignalTimer;
|
|
35
|
+
const clearDelayedSignal = () => {
|
|
36
|
+
if (delayedSignalTimer === undefined)
|
|
37
|
+
return;
|
|
38
|
+
clearTimeout(delayedSignalTimer);
|
|
39
|
+
delayedSignalTimer = undefined;
|
|
40
|
+
};
|
|
41
|
+
const forwardSignal = (signal) => {
|
|
42
|
+
if (childClosed)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
child.kill(signal);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// best-effort
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const scheduleSignalForward = (signal) => {
|
|
52
|
+
if (childClosed || delayedSignalTimer !== undefined)
|
|
53
|
+
return;
|
|
54
|
+
delayedSignalTimer = globalThis.setTimeout(() => {
|
|
55
|
+
delayedSignalTimer = undefined;
|
|
56
|
+
forwardSignal(signal);
|
|
57
|
+
}, 1500);
|
|
58
|
+
delayedSignalTimer.unref?.();
|
|
59
|
+
};
|
|
60
|
+
const onSigint = () => {
|
|
61
|
+
if (process.stdin.isTTY === true) {
|
|
62
|
+
scheduleSignalForward('SIGINT');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
forwardSignal('SIGINT');
|
|
66
|
+
};
|
|
67
|
+
const onSigterm = () => {
|
|
68
|
+
if (process.stdin.isTTY === true) {
|
|
69
|
+
scheduleSignalForward('SIGTERM');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
forwardSignal('SIGTERM');
|
|
73
|
+
};
|
|
74
|
+
process.on('SIGINT', onSigint);
|
|
75
|
+
process.on('SIGTERM', onSigterm);
|
|
27
76
|
const result = await new Promise((resolve, reject) => {
|
|
28
77
|
let settled = false;
|
|
29
|
-
|
|
78
|
+
let childResult = {
|
|
79
|
+
exitCode: null,
|
|
80
|
+
signal: null,
|
|
81
|
+
};
|
|
82
|
+
const finalize = () => {
|
|
30
83
|
if (settled) {
|
|
31
84
|
return;
|
|
32
85
|
}
|
|
33
86
|
settled = true;
|
|
87
|
+
childClosed = true;
|
|
88
|
+
clearDelayedSignal();
|
|
89
|
+
process.off('SIGINT', onSigint);
|
|
90
|
+
process.off('SIGTERM', onSigterm);
|
|
34
91
|
child.off?.('error', reject);
|
|
35
|
-
child.off?.('exit',
|
|
36
|
-
child.off?.('close',
|
|
37
|
-
resolve(
|
|
92
|
+
child.off?.('exit', handleExit);
|
|
93
|
+
child.off?.('close', handleClose);
|
|
94
|
+
resolve(childResult);
|
|
95
|
+
};
|
|
96
|
+
const handleExit = (exitCode, signal) => {
|
|
97
|
+
childResult = { exitCode, signal };
|
|
98
|
+
};
|
|
99
|
+
const handleClose = (exitCode, signal) => {
|
|
100
|
+
childResult = {
|
|
101
|
+
exitCode: childResult.exitCode ?? exitCode,
|
|
102
|
+
signal: childResult.signal ?? signal,
|
|
103
|
+
};
|
|
104
|
+
finalize();
|
|
38
105
|
};
|
|
39
106
|
child.once('error', reject);
|
|
40
|
-
child.once('exit',
|
|
41
|
-
child.once('close',
|
|
107
|
+
child.once('exit', handleExit);
|
|
108
|
+
child.once('close', handleClose);
|
|
42
109
|
});
|
|
43
110
|
process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
|
package/bin/zt.js
CHANGED
|
@@ -15,13 +15,90 @@ const nodeArgs = target.endsWith('.ts')
|
|
|
15
15
|
? ['--import', 'tsx', target, ...process.argv.slice(2)]
|
|
16
16
|
: [target, ...process.argv.slice(2)];
|
|
17
17
|
const child = spawn(process.execPath, nodeArgs, {
|
|
18
|
-
stdio: 'inherit',
|
|
18
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
19
19
|
env: process.env,
|
|
20
20
|
});
|
|
21
|
+
child.stdout?.on('data', (chunk) => {
|
|
22
|
+
process.stdout.write(chunk);
|
|
23
|
+
});
|
|
24
|
+
child.stderr?.on('data', (chunk) => {
|
|
25
|
+
process.stderr.write(chunk);
|
|
26
|
+
});
|
|
27
|
+
let childClosed = false;
|
|
28
|
+
let delayedSignalTimer;
|
|
29
|
+
const clearDelayedSignal = () => {
|
|
30
|
+
if (delayedSignalTimer === undefined)
|
|
31
|
+
return;
|
|
32
|
+
clearTimeout(delayedSignalTimer);
|
|
33
|
+
delayedSignalTimer = undefined;
|
|
34
|
+
};
|
|
35
|
+
const forwardSignal = (signal) => {
|
|
36
|
+
if (childClosed)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
child.kill(signal);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// best-effort
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const scheduleSignalForward = (signal) => {
|
|
46
|
+
if (childClosed || delayedSignalTimer !== undefined)
|
|
47
|
+
return;
|
|
48
|
+
delayedSignalTimer = globalThis.setTimeout(() => {
|
|
49
|
+
delayedSignalTimer = undefined;
|
|
50
|
+
forwardSignal(signal);
|
|
51
|
+
}, 1500);
|
|
52
|
+
delayedSignalTimer.unref?.();
|
|
53
|
+
};
|
|
54
|
+
const onSigint = () => {
|
|
55
|
+
if (process.stdin.isTTY === true) {
|
|
56
|
+
scheduleSignalForward('SIGINT');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
forwardSignal('SIGINT');
|
|
60
|
+
};
|
|
61
|
+
const onSigterm = () => {
|
|
62
|
+
if (process.stdin.isTTY === true) {
|
|
63
|
+
scheduleSignalForward('SIGTERM');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
forwardSignal('SIGTERM');
|
|
67
|
+
};
|
|
68
|
+
process.on('SIGINT', onSigint);
|
|
69
|
+
process.on('SIGTERM', onSigterm);
|
|
21
70
|
const result = await new Promise((resolve, reject) => {
|
|
71
|
+
let settled = false;
|
|
72
|
+
let childResult = {
|
|
73
|
+
exitCode: null,
|
|
74
|
+
signal: null,
|
|
75
|
+
};
|
|
76
|
+
const finalize = () => {
|
|
77
|
+
if (settled) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
settled = true;
|
|
81
|
+
childClosed = true;
|
|
82
|
+
clearDelayedSignal();
|
|
83
|
+
process.off('SIGINT', onSigint);
|
|
84
|
+
process.off('SIGTERM', onSigterm);
|
|
85
|
+
child.off?.('error', reject);
|
|
86
|
+
child.off?.('exit', handleExit);
|
|
87
|
+
child.off?.('close', handleClose);
|
|
88
|
+
resolve(childResult);
|
|
89
|
+
};
|
|
90
|
+
const handleExit = (exitCode, signal) => {
|
|
91
|
+
childResult = { exitCode, signal };
|
|
92
|
+
};
|
|
93
|
+
const handleClose = (exitCode, signal) => {
|
|
94
|
+
childResult = {
|
|
95
|
+
exitCode: childResult.exitCode ?? exitCode,
|
|
96
|
+
signal: childResult.signal ?? signal,
|
|
97
|
+
};
|
|
98
|
+
finalize();
|
|
99
|
+
};
|
|
22
100
|
child.once('error', reject);
|
|
23
|
-
child.once('
|
|
24
|
-
|
|
25
|
-
});
|
|
101
|
+
child.once('exit', handleExit);
|
|
102
|
+
child.once('close', handleClose);
|
|
26
103
|
});
|
|
27
104
|
process.exit(result.exitCode ?? (result.signal === 'SIGINT' || result.signal === 'SIGTERM' ? 0 : 1));
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgbH,eAAO,MAAM,cAAc,eAAmB,CAAC"}
|
package/src/boot/bootstrap.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Env } from '../config/env.js';
|
|
|
11
11
|
import { Logger } from '../config/logger.js';
|
|
12
12
|
import { shutdownRedisConnections } from '../config/workers.js';
|
|
13
13
|
import { ErrorFactory } from '../exceptions/ZintrustError.js';
|
|
14
|
+
import { ShutdownTrace } from '../helper/ShutdownTrace.js';
|
|
14
15
|
import { ProjectRuntime } from '../runtime/ProjectRuntime.js';
|
|
15
16
|
import { StartupErrorLogging } from '../runtime/StartupErrorLogging.js';
|
|
16
17
|
import { WorkerProjectAutoImports } from '../runtime/WorkerProjectAutoImports.js';
|
|
@@ -92,66 +93,130 @@ const withTimeout = async (promise, timeoutMs, label) => {
|
|
|
92
93
|
globalThis.clearTimeout(timeoutId);
|
|
93
94
|
}
|
|
94
95
|
};
|
|
96
|
+
const shutdownWorkersIfNeeded = async (signal, remainingMs) => {
|
|
97
|
+
if (appConfig.worker !== true ||
|
|
98
|
+
(appConfig.detectRuntime() !== 'nodejs' && appConfig.detectRuntime() !== 'lambda') ||
|
|
99
|
+
appConfig.dockerWorker === true) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
ShutdownTrace.log('bootstrap.graceful-shutdown.worker-shutdown.start', {
|
|
104
|
+
signal,
|
|
105
|
+
remainingMs: remainingMs(),
|
|
106
|
+
});
|
|
107
|
+
const workers = await loadWorkersModule();
|
|
108
|
+
const workerBudgetMs = Math.min(15000, remainingMs());
|
|
109
|
+
await withTimeout(workers.WorkerShutdown.shutdown({
|
|
110
|
+
signal,
|
|
111
|
+
timeout: workerBudgetMs,
|
|
112
|
+
forceExit: false,
|
|
113
|
+
}), workerBudgetMs, 'Worker shutdown timed out');
|
|
114
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.worker-shutdown.complete', {
|
|
115
|
+
workerBudgetMs,
|
|
116
|
+
remainingMs: remainingMs(),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
|
|
121
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.worker-shutdown.failed', {
|
|
122
|
+
remainingMs: remainingMs(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const shutdownServerIfNeeded = async (remainingMs) => {
|
|
127
|
+
if (serverInstance === undefined)
|
|
128
|
+
return;
|
|
129
|
+
ShutdownTrace.log('bootstrap.graceful-shutdown.server-close.start', {
|
|
130
|
+
remainingMs: remainingMs(),
|
|
131
|
+
});
|
|
132
|
+
await serverInstance.close();
|
|
133
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.server-close.complete', {
|
|
134
|
+
remainingMs: remainingMs(),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const shutdownAppIfNeeded = async (remainingMs) => {
|
|
138
|
+
if (appInstance === undefined)
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
const appBudgetMs = Math.min(5000, remainingMs());
|
|
142
|
+
ShutdownTrace.log('bootstrap.graceful-shutdown.app-shutdown.start', {
|
|
143
|
+
appBudgetMs,
|
|
144
|
+
remainingMs: remainingMs(),
|
|
145
|
+
});
|
|
146
|
+
await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
|
|
147
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.app-shutdown.complete', {
|
|
148
|
+
appBudgetMs,
|
|
149
|
+
remainingMs: remainingMs(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
Logger.warn('App shutdown failed or timed out, forcing exit', error);
|
|
154
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.app-shutdown.failed', {
|
|
155
|
+
remainingMs: remainingMs(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const shutdownTrackedRedis = async (remainingMs) => {
|
|
160
|
+
try {
|
|
161
|
+
const redisBudgetMs = Math.max(250, Math.min(3000, remainingMs()));
|
|
162
|
+
ShutdownTrace.log('bootstrap.graceful-shutdown.redis-shutdown.start', {
|
|
163
|
+
redisBudgetMs,
|
|
164
|
+
remainingMs: remainingMs(),
|
|
165
|
+
});
|
|
166
|
+
await withTimeout(shutdownRedisConnections(), redisBudgetMs, 'Redis connection shutdown timed out');
|
|
167
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.redis-shutdown.complete', {
|
|
168
|
+
redisBudgetMs,
|
|
169
|
+
remainingMs: remainingMs(),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
Logger.warn('Redis connection shutdown failed (continuing with app shutdown)', error);
|
|
174
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.redis-shutdown.failed', {
|
|
175
|
+
remainingMs: remainingMs(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const runGracefulShutdownPhases = async (signal, remainingMs) => {
|
|
180
|
+
await shutdownWorkersIfNeeded(signal, remainingMs);
|
|
181
|
+
await shutdownServerIfNeeded(remainingMs);
|
|
182
|
+
await shutdownAppIfNeeded(remainingMs);
|
|
183
|
+
await shutdownTrackedRedis(remainingMs);
|
|
184
|
+
Logger.info('✅ Application shut down successfully');
|
|
185
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.complete', {
|
|
186
|
+
remainingMs: remainingMs(),
|
|
187
|
+
});
|
|
188
|
+
};
|
|
95
189
|
const gracefulShutdown = async (signal) => {
|
|
96
190
|
if (isShuttingDown)
|
|
97
191
|
return;
|
|
98
192
|
isShuttingDown = true;
|
|
99
|
-
const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT',
|
|
193
|
+
const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT', 10000);
|
|
100
194
|
const minForceExitMs = shutdownBudgetMs + 250;
|
|
101
195
|
const forceExitMs = Math.max(Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 10000), minForceExitMs);
|
|
102
196
|
const deadlineMs = Date.now() + shutdownBudgetMs;
|
|
103
197
|
const remainingMs = () => Math.max(0, deadlineMs - Date.now());
|
|
104
198
|
Logger.info(`${signal} received, shutting down gracefully...`);
|
|
199
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.received', {
|
|
200
|
+
signal,
|
|
201
|
+
shutdownBudgetMs,
|
|
202
|
+
forceExitMs,
|
|
203
|
+
workerEnabled: appConfig.worker,
|
|
204
|
+
});
|
|
105
205
|
try {
|
|
106
206
|
const forceExitTimer = globalThis.setTimeout(() => {
|
|
107
207
|
process.exit(0);
|
|
108
208
|
}, forceExitMs);
|
|
109
209
|
// Best-effort: don't keep the process alive just for this timer
|
|
110
210
|
forceExitTimer.unref?.();
|
|
111
|
-
await withTimeout((
|
|
112
|
-
// Shutdown worker management system FIRST (before database closes)
|
|
113
|
-
if (appConfig.worker === true &&
|
|
114
|
-
(appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') &&
|
|
115
|
-
appConfig.dockerWorker === false) {
|
|
116
|
-
try {
|
|
117
|
-
const workers = await loadWorkersModule();
|
|
118
|
-
const workerBudgetMs = Math.min(15000, remainingMs());
|
|
119
|
-
await withTimeout(workers.WorkerShutdown.shutdown({
|
|
120
|
-
signal,
|
|
121
|
-
timeout: workerBudgetMs,
|
|
122
|
-
forceExit: false,
|
|
123
|
-
}), workerBudgetMs, 'Worker shutdown timed out');
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (serverInstance !== undefined) {
|
|
130
|
-
await serverInstance.close();
|
|
131
|
-
}
|
|
132
|
-
if (appInstance !== undefined) {
|
|
133
|
-
try {
|
|
134
|
-
const appBudgetMs = Math.min(5000, remainingMs());
|
|
135
|
-
await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
Logger.warn('App shutdown failed or timed out, forcing exit', error);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
const redisBudgetMs = Math.max(250, Math.min(3000, remainingMs()));
|
|
143
|
-
await withTimeout(shutdownRedisConnections(), redisBudgetMs, 'Redis connection shutdown timed out');
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
Logger.warn('Redis connection shutdown failed (continuing with app shutdown)', error);
|
|
147
|
-
}
|
|
148
|
-
Logger.info('✅ Application shut down successfully');
|
|
149
|
-
})(), shutdownBudgetMs, 'Graceful shutdown timed out');
|
|
211
|
+
await withTimeout(runGracefulShutdownPhases(signal, remainingMs), shutdownBudgetMs, 'Graceful shutdown timed out');
|
|
150
212
|
globalThis.clearTimeout(forceExitTimer);
|
|
151
213
|
process.exit(0);
|
|
152
214
|
}
|
|
153
215
|
catch (error) {
|
|
154
216
|
Logger.error('Graceful shutdown failed:', error);
|
|
217
|
+
ShutdownTrace.logHandles('bootstrap.graceful-shutdown.error', {
|
|
218
|
+
error: error instanceof Error ? error.message : String(error),
|
|
219
|
+
});
|
|
155
220
|
process.exit(1);
|
|
156
221
|
}
|
|
157
222
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../src/boot/registry/runtime.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAUvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAkQ9C,eAAO,MAAM,8BAA8B,GAAI,iBAAiB,gBAAgB,KAAG,IA6BlF,CAAC;AA8UF,eAAO,MAAM,eAAe,GAAI,QAAQ;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,KAAG;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAsH7D,CAAC"}
|