Skip to content

Commit f234b1b

Browse files
authored
feat: support using process.on to listen SIGTERM for 1.x (#9)
1 parent 336fef4 commit f234b1b

5 files changed

Lines changed: 154 additions & 55 deletions

File tree

index.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,23 @@ module.exports = (options = {}) => {
3535

3636
// https://github.com/eggjs/egg-cluster/blob/master/lib/agent_worker.js#L35
3737
// exit gracefully
38-
process.once('SIGTERM', () => {
39-
printLogLevels.info && logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
40-
exit(0);
41-
});
38+
if (options.sigterm === 'always') {
39+
let called = false;
40+
process.on('SIGTERM', () => {
41+
if (called) {
42+
printLogLevels.info && logger.info('[%s] receive signal SIGTERM again, waiting for exit', label);
43+
return;
44+
}
45+
called = true;
46+
printLogLevels.info && logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
47+
exit(0);
48+
});
49+
} else {
50+
process.once('SIGTERM', () => {
51+
printLogLevels.info && logger.info('[%s] receive signal SIGTERM, exiting with code:0', label);
52+
exit(0);
53+
});
54+
}
4255

4356
process.once('exit', code => {
4457
const level = code === 0 ? 'info' : 'error';

test/fixtures/child.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ http.createServer((req, res) => {
88
}).listen(8000);
99

1010
console.log(`Worker ${process.pid} started`);
11-
require('../..')({
11+
const options = {
1212
logger: console,
1313
label: 'test-child',
1414
logLevel: process.env.NODE_LOG_LEVEL,
15-
});
15+
};
16+
if (process.env.ALWAYS_ON_SIGTERM) {
17+
options.sigterm = 'always';
18+
options.beforeExit = async () => {
19+
await new Promise(r => setTimeout(r, 1000));
20+
console.log('exit after 1000ms');
21+
};
22+
}
23+
require('../..')(options);
1624
// run again should work
1725
require('../..')();

test/fixtures/cluster.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@ if (cluster.isMaster) {
1616
});
1717

1818
process.once('SIGTERM', () => {
19-
for (const id in cluster.workers) {
20-
cluster.workers[id].process.kill('SIGTERM');
19+
const killWorkers = () => {
20+
for (const id in cluster.workers) {
21+
cluster.workers[id].process.kill('SIGTERM');
22+
}
23+
};
24+
25+
killWorkers();
26+
27+
if (process.env.ALWAYS_ON_SIGTERM) {
28+
setTimeout(killWorkers, 50);
29+
setTimeout(() => process.exit(0), 2100);
30+
} else {
31+
setTimeout(() => {
32+
process.exit(0);
33+
}, 100);
2134
}
22-
setTimeout(() => {
23-
process.exit(0);
24-
}, 100);
2535
});
2636
} else {
2737
// Workers can share any TCP connection
@@ -35,8 +45,16 @@ if (cluster.isMaster) {
3545
}).listen(8000);
3646

3747
console.log(`Worker ${process.pid} started`);
38-
require('../..')({
48+
const options = {
3949
label: 'app-worker-' + cluster.worker.id,
4050
logLevel: process.env.NODE_LOG_LEVEL,
41-
});
51+
};
52+
if (process.env.ALWAYS_ON_SIGTERM) {
53+
options.sigterm = 'always';
54+
options.beforeExit = async () => {
55+
await new Promise(r => setTimeout(r, 1000));
56+
console.log('exit after 1000ms');
57+
};
58+
}
59+
require('../..')(options);
4260
}

test/fixtures/master-sigterm.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const childProcess = require('child_process');
4+
const path = require('path');
5+
6+
const childFile = path.join(__dirname, 'child.js');
7+
8+
const child = childProcess.fork(childFile);
9+
10+
process.once('SIGTERM', () => {
11+
child.kill('SIGTERM');
12+
setTimeout(() => child.kill('SIGTERM'), 50);
13+
setTimeout(() => process.exit(0), 1500);
14+
});
15+
16+
console.log('master fork %s done', childFile);

test/index.test.js

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,56 +12,56 @@ const waitStart = process.env.COV ? 5000 : 2000;
1212

1313
describe('test/index.test.js', () => {
1414
describe('cluster', () => {
15-
it('should workers auto exit when master kill by SIGKILL', function* () {
15+
it('should workers auto exit when master kill by SIGKILL', async () => {
1616
const startFile = path.join(fixtures, 'cluster.js');
1717
const child = coffee.fork(startFile)
1818
.debug();
19-
yield sleep(waitStart);
20-
const result = yield urllib.request('http://127.0.0.1:8000/');
19+
await sleep(waitStart);
20+
const result = await urllib.request('http://127.0.0.1:8000/');
2121
assert(result.status === 200);
2222
assert(result.data.toString() === 'hello world\n');
2323
// all workers exit by cluster
2424
child.proc.kill('SIGKILL');
25-
yield sleep(1000);
25+
await sleep(1000);
2626
child.expect('stderr', /\[app-worker-1\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
2727
child.expect('stderr', /\[app-worker-2\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
2828
child.expect('stdout', /\[app-worker-1\] exit with code:0/);
2929
child.expect('stdout', /\[app-worker-2\] exit with code:0/);
3030
});
3131

32-
it('should don\'t print info log', function* () {
32+
it('should don\'t print info log', async () => {
3333
const startFile = path.join(fixtures, 'cluster.js');
3434
const child = coffee.fork(startFile, { env: { NODE_LOG_LEVEL: 'warn' } })
3535
.debug();
36-
yield sleep(waitStart);
37-
const result = yield urllib.request('http://127.0.0.1:8000/');
36+
await sleep(waitStart);
37+
const result = await urllib.request('http://127.0.0.1:8000/');
3838
assert(result.status === 200);
3939
assert(result.data.toString() === 'hello world\n');
4040
// all workers exit by cluster
4141
child.proc.kill('SIGKILL');
42-
yield sleep(1000);
42+
await sleep(1000);
4343
child.expect('stderr', /\[app-worker-1\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
4444
child.expect('stderr', /\[app-worker-2\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
4545
child.notExpect('stdout', /\[app-worker-1\] exit with code:0/);
4646
child.notExpect('stdout', /\[app-worker-2\] exit with code:0/);
4747
});
4848

49-
it('should workers auto exit when master kill by SIGTERM', function* () {
49+
it('should workers auto exit when master kill by SIGTERM', async () => {
5050
const startFile = path.join(fixtures, 'cluster.js');
5151
const child = coffee.fork(startFile)
5252
.debug();
53-
yield sleep(waitStart);
54-
const result = yield urllib.request('http://127.0.0.1:8000/');
53+
await sleep(waitStart);
54+
const result = await urllib.request('http://127.0.0.1:8000/');
5555
assert(result.status === 200);
5656
assert(result.data.toString() === 'hello world\n');
5757
// suicide
58-
const result2 = yield urllib.request('http://127.0.0.1:8000/suicide');
58+
const result2 = await urllib.request('http://127.0.0.1:8000/suicide');
5959
assert(result2.status === 200);
6060
assert(result2.data.toString() === 'hello world\n');
61-
yield sleep(1000);
61+
await sleep(1000);
6262
// make sure all workers exit by itself after SIGTERM event fired
6363
child.proc.kill('SIGTERM');
64-
yield sleep(2000);
64+
await sleep(2000);
6565
child.notExpect('stderr', /\[app-worker-1\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
6666
child.notExpect('stderr', /\[app-worker-2\] receive disconnect event in cluster fork mode, exitedAfterDisconnect:false/);
6767
if (process.platform !== 'win32') {
@@ -72,39 +72,83 @@ describe('test/index.test.js', () => {
7272
child.expect('stdout', /\[app-worker-2\] exit with code:0/);
7373
child.expect('stdout', /worker \d+ died, code 0, signal null/);
7474
});
75+
76+
it('should always listen sigterm work', async () => {
77+
const startFile = path.join(fixtures, 'cluster.js');
78+
const child = coffee.fork(startFile, [], {
79+
env: {
80+
...process.env,
81+
ALWAYS_ON_SIGTERM: 'Y',
82+
},
83+
})
84+
.debug();
85+
await sleep(waitStart);
86+
child.proc.kill('SIGTERM');
87+
await sleep(2200);
88+
if (process.platform !== 'win32') {
89+
// windows can't handle SIGTERM signal
90+
child.expect('stdout', /\[app-worker-\d\] receive signal SIGTERM, exiting with code:0/);
91+
child.expect('stdout', /\[app-worker-\d\] receive signal SIGTERM again, waiting for exit/);
92+
child.expect('stdout', /exit after 1000ms/);
93+
}
94+
child.expect('stdout', /\[app-worker-1\] exit with code:0/);
95+
child.expect('stdout', /\[app-worker-2\] exit with code:0/);
96+
});
7597
});
7698

7799
describe('child_process.fork', () => {
78-
it('should worker exit after master kill by SIGKILL', function* () {
100+
it('should worker exit after master kill by SIGKILL', async () => {
79101
const startFile = path.join(fixtures, 'master.js');
80102
const child = coffee.fork(startFile)
81103
.debug();
82-
yield sleep(waitStart);
83-
const result = yield urllib.request('http://127.0.0.1:8000/');
104+
await sleep(waitStart);
105+
const result = await urllib.request('http://127.0.0.1:8000/');
84106
assert(result.status === 200);
85107
assert(result.data.toString() === 'hello world\n');
86108
// the worker exit by graceful-process
87109
child.proc.kill('SIGKILL');
88-
yield sleep(2000);
110+
await sleep(2000);
89111
if (process.platform !== 'win32') {
90112
child.expect('stderr', /\[test-child\] receive disconnect event on child_process fork mode, exiting with code:110/);
91113
child.expect('stderr', /\[test-child\] exit with code:110/);
92114
}
93115
});
116+
117+
it('should always listen sigterm work', async () => {
118+
const startFile = path.join(fixtures, 'master-sigterm.js');
119+
const child = coffee.fork(startFile, [], {
120+
env: {
121+
...process.env,
122+
ALWAYS_ON_SIGTERM: 'Y',
123+
},
124+
})
125+
.debug();
126+
await sleep(waitStart);
127+
// the worker exit by graceful-process
128+
child.proc.kill('SIGTERM');
129+
await sleep(2000);
130+
if (process.platform !== 'win32') {
131+
// windows can't handle SIGTERM signal
132+
child.expect('stdout', /\[test-child\] receive signal SIGTERM, exiting with code:0/);
133+
child.expect('stdout', /\[test-child\] receive signal SIGTERM again, waiting for exit/);
134+
child.expect('stdout', /exit after 1000ms/);
135+
child.expect('stdout', /\[test-child\] exit with code:0/);
136+
}
137+
});
94138
});
95139

96140
describe.skip('child_process.spawn', () => {
97-
it('should worker exit after master kill by SIGKILL', function* () {
141+
it('should worker exit after master kill by SIGKILL', async () => {
98142
const startFile = path.join(fixtures, 'master-spawn.js');
99143
const child = coffee.fork(startFile)
100144
.debug();
101-
yield sleep(waitStart);
102-
const result = yield urllib.request('http://127.0.0.1:8000/');
145+
await sleep(waitStart);
146+
const result = await urllib.request('http://127.0.0.1:8000/');
103147
assert(result.status === 200);
104148
assert(result.data.toString() === 'hello world\n');
105149
// the worker exit by graceful-process
106150
child.proc.kill('SIGKILL');
107-
yield sleep(1000);
151+
await sleep(1000);
108152
child.expect('stderr', /222receive disconnect event on child_process fork mode, exiting with code:110/);
109153
child.expect('stderr', /222exit with code:110/);
110154
});
@@ -113,81 +157,81 @@ describe('test/index.test.js', () => {
113157
describe('beforeExit', () => {
114158
afterEach(mm.restore);
115159

116-
it('should support normal function', function* () {
160+
it('should support normal function', async () => {
117161
mm(process.env, 'MODE', '');
118162
const startFile = path.join(fixtures, 'before-exit.js');
119163
const child = coffee.fork(startFile)
120164
.debug();
121-
yield sleep(waitStart);
122-
const result = yield urllib.request('http://127.0.0.1:8000/');
165+
await sleep(waitStart);
166+
const result = await urllib.request('http://127.0.0.1:8000/');
123167
assert(result.status === 200);
124168

125169
child.proc.kill();
126-
yield sleep(5000);
170+
await sleep(5000);
127171
child.expect('stdout', /process exit/);
128172
child.expect('stdout', /exit with code:0/);
129173
});
130174

131-
it('should support function return promise', function* () {
175+
it('should support function return promise', async () => {
132176
mm(process.env, 'MODE', 'promise');
133177
const startFile = path.join(fixtures, 'before-exit.js');
134178
const child = coffee.fork(startFile)
135179
.debug();
136-
yield sleep(waitStart);
137-
const result = yield urllib.request('http://127.0.0.1:8000/');
180+
await sleep(waitStart);
181+
const result = await urllib.request('http://127.0.0.1:8000/');
138182
assert(result.status === 200);
139183

140184
child.proc.kill();
141-
yield sleep(5000);
185+
await sleep(5000);
142186
child.expect('stdout', /beforeExit success/);
143187
child.expect('stdout', /process exiting\nprocess exited/);
144188
child.expect('stdout', /exit with code:0/);
145189
});
146190

147191
if (parseInt(process.versions.node) < 8) return;
148192

149-
it('should support async function', function* () {
193+
it('should support async function', async () => {
150194
mm(process.env, 'MODE', 'async');
151195
const startFile = path.join(fixtures, 'before-exit.js');
152196
const child = coffee.fork(startFile)
153197
.debug();
154-
yield sleep(waitStart);
155-
const result = yield urllib.request('http://127.0.0.1:8000/');
198+
await sleep(waitStart);
199+
const result = await urllib.request('http://127.0.0.1:8000/');
156200
assert(result.status === 200);
157201

158202
child.proc.kill();
159-
yield sleep(5000);
203+
await sleep(5000);
160204
child.expect('stdout', /beforeExit success/);
161205
child.expect('stdout', /process exiting\nprocess exited/);
162206
child.expect('stdout', /exit with code:0/);
163207
});
164208

165-
it('should exit when async error', function* () {
209+
it('should exit when async error', async () => {
166210
mm(process.env, 'MODE', 'async-error');
167211
const startFile = path.join(fixtures, 'before-exit.js');
168212
const child = coffee.fork(startFile)
169213
.debug();
170-
yield sleep(waitStart);
171-
const result = yield urllib.request('http://127.0.0.1:8000/');
214+
await sleep(waitStart);
215+
const result = await urllib.request('http://127.0.0.1:8000/');
172216
assert(result.status === 200);
173217

174218
child.proc.kill();
175-
yield sleep(5000);
219+
await sleep(5000);
176220
child.expect('stderr', /beforeExit fail, error: reject/);
177221
child.expect('stdout', /exit with code:0/);
178222
});
179223

180-
it('should exit when function error', function* () {
224+
it('should exit when function error', async () => {
181225
mm(process.env, 'MODE', 'function-error');
182226
const startFile = path.join(fixtures, 'before-exit.js');
183227
const child = coffee.fork(startFile)
184228
.debug();
185-
yield sleep(waitStart);
186-
const result = yield urllib.request('http://127.0.0.1:8000/');
229+
await sleep(waitStart);
230+
const result = await urllib.request('http://127.0.0.1:8000/');
187231
assert(result.status === 200);
188232

189233
child.proc.kill();
190-
yield sleep(5000);
234+
await sleep(5000);
191235
child.expect('stderr', /beforeExit fail, error: process exit/);
192236
child.expect('stdout', /exit with code:0/);
193237
});

0 commit comments

Comments
 (0)