1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var debug = require('debug')('pm2:daemon');
|
8 | var pkg = require('../package.json');
|
9 | var cst = require('../constants.js');
|
10 | var rpc = require('pm2-axon-rpc');
|
11 | var axon = require('pm2-axon');
|
12 | var domain = require('domain');
|
13 | var Utility = require('./Utility.js');
|
14 | var util = require('util');
|
15 | var fs = require('fs');
|
16 | var God = require('./God');
|
17 | var eachLimit = require('async/eachLimit');
|
18 | var fmt = require('./tools/fmt.js');
|
19 | var semver = require('semver');
|
20 |
|
21 | var Daemon = module.exports = function(opts) {
|
22 | if (!opts) opts = {};
|
23 |
|
24 | this.ignore_signals = opts.ignore_signals || false;
|
25 | this.rpc_socket_ready = false;
|
26 | this.pub_socket_ready = false;
|
27 |
|
28 | this.pub_socket_file = opts.pub_socket_file || cst.DAEMON_PUB_PORT;
|
29 | this.rpc_socket_file = opts.rpc_socket_file || cst.DAEMON_RPC_PORT;
|
30 |
|
31 | this.pid_path = opts.pid_file || cst.PM2_PID_FILE_PATH;
|
32 | };
|
33 |
|
34 | Daemon.prototype.start = function() {
|
35 | var that = this;
|
36 | var d = domain.create();
|
37 |
|
38 | d.once('error', function(err) {
|
39 | fmt.sep();
|
40 | fmt.title('PM2 global error caught');
|
41 | fmt.field('Time', new Date());
|
42 | console.error(err.message);
|
43 | console.error(err.stack);
|
44 | fmt.sep();
|
45 |
|
46 | console.error('[PM2] Resurrecting PM2');
|
47 |
|
48 | var path = cst.IS_WINDOWS ? __dirname + '/../bin/pm2' : process.env['_'];
|
49 | var fork_new_pm2 = require('child_process').spawn('node', [path, 'update'], {
|
50 | detached: true,
|
51 | windowsHide: true,
|
52 | stdio: 'inherit'
|
53 | });
|
54 |
|
55 | fork_new_pm2.on('close', function() {
|
56 | console.log('PM2 successfully forked');
|
57 | process.exit(0);
|
58 | })
|
59 |
|
60 | });
|
61 |
|
62 | d.run(function() {
|
63 | that.innerStart();
|
64 | });
|
65 | }
|
66 |
|
67 | Daemon.prototype.innerStart = function(cb) {
|
68 | var that = this;
|
69 |
|
70 | if (!cb) cb = function() {
|
71 | fmt.sep();
|
72 | fmt.title('New PM2 Daemon started');
|
73 | fmt.field('Time', new Date());
|
74 | fmt.field('PM2 version', pkg.version);
|
75 | fmt.field('Node.js version', process.versions.node);
|
76 | fmt.field('Current arch', process.arch);
|
77 | fmt.field('PM2 home', cst.PM2_HOME);
|
78 | fmt.field('PM2 PID file', that.pid_path);
|
79 | fmt.field('RPC socket file', that.rpc_socket_file);
|
80 | fmt.field('BUS socket file', that.pub_socket_file);
|
81 | fmt.field('Application log path', cst.DEFAULT_LOG_PATH);
|
82 | fmt.field('Worker Interval', cst.WORKER_INTERVAL);
|
83 | fmt.field('Process dump file', cst.DUMP_FILE_PATH);
|
84 | fmt.field('Concurrent actions', cst.CONCURRENT_ACTIONS);
|
85 | fmt.field('SIGTERM timeout', cst.KILL_TIMEOUT);
|
86 | fmt.sep();
|
87 | };
|
88 |
|
89 |
|
90 | try {
|
91 | fs.writeFileSync(that.pid_path, process.pid.toString());
|
92 | } catch (e) {
|
93 | console.error(e.stack || e);
|
94 | }
|
95 |
|
96 | if (this.ignore_signals != true)
|
97 | this.handleSignals();
|
98 |
|
99 | |
100 |
|
101 |
|
102 | this.pub = axon.socket('pub-emitter');
|
103 |
|
104 | this.pub_socket = this.pub.bind(this.pub_socket_file);
|
105 |
|
106 | this.pub_socket.once('bind', function() {
|
107 | fs.chmod(that.pub_socket_file, '775', function(e) {
|
108 | if (e) console.error(e);
|
109 |
|
110 | try {
|
111 | if (process.env.PM2_SOCKET_USER && process.env.PM2_SOCKET_GROUP)
|
112 | fs.chown(that.pub_socket_file,
|
113 | parseInt(process.env.PM2_SOCKET_USER),
|
114 | parseInt(process.env.PM2_SOCKET_GROUP), function(e) {
|
115 | if (e) console.error(e);
|
116 | });
|
117 | } catch(e) {
|
118 | console.error(e);
|
119 | }
|
120 | });
|
121 |
|
122 | that.pub_socket_ready = true;
|
123 | that.sendReady(cb);
|
124 | });
|
125 |
|
126 | |
127 |
|
128 |
|
129 | this.rep = axon.socket('rep');
|
130 |
|
131 | var server = new rpc.Server(this.rep);
|
132 |
|
133 | this.rpc_socket = this.rep.bind(this.rpc_socket_file);
|
134 |
|
135 | this.rpc_socket.once('bind', function() {
|
136 | fs.chmod(that.rpc_socket_file, '775', function(e) {
|
137 | if (e) console.error(e);
|
138 |
|
139 | try {
|
140 | if (process.env.PM2_SOCKET_USER && process.env.PM2_SOCKET_GROUP)
|
141 | fs.chown(that.rpc_socket_file,
|
142 | parseInt(process.env.PM2_SOCKET_USER),
|
143 | parseInt(process.env.PM2_SOCKET_GROUP), function(e) {
|
144 | if (e) console.error(e);
|
145 | });
|
146 | } catch(e) {
|
147 | console.error(e);
|
148 | }
|
149 | });
|
150 |
|
151 |
|
152 | that.rpc_socket_ready = true;
|
153 | that.sendReady(cb);
|
154 | });
|
155 |
|
156 |
|
157 | |
158 |
|
159 |
|
160 | function profile(type, msg, cb) {
|
161 | if (semver.satisfies(process.version, '< 8'))
|
162 | return cb(null, { error: 'Node.js is not on right version' })
|
163 |
|
164 | var cmd
|
165 |
|
166 | if (type === 'cpu') {
|
167 | cmd = {
|
168 | enable: 'Profiler.enable',
|
169 | start: 'Profiler.start',
|
170 | stop: 'Profiler.stop',
|
171 | disable: 'Profiler.disable'
|
172 | }
|
173 | }
|
174 | if (type == 'mem') {
|
175 | cmd = {
|
176 | enable: 'HeapProfiler.enable',
|
177 | start: 'HeapProfiler.startSampling',
|
178 | stop: 'HeapProfiler.stopSampling',
|
179 | disable: 'HeapProfiler.disable'
|
180 | }
|
181 | }
|
182 |
|
183 | const inspector = require('inspector')
|
184 | var session = new inspector.Session()
|
185 |
|
186 | session.connect()
|
187 |
|
188 | var timeout = msg.timeout || 5000
|
189 |
|
190 | session.post(cmd.enable, (err, data) => {
|
191 | if (err) return cb(null, { error: err.message || err })
|
192 |
|
193 | console.log(`Starting ${cmd.start}`)
|
194 | session.post(cmd.start, (err, data) => {
|
195 | if (err) return cb(null, { error: err.message || err })
|
196 |
|
197 | setTimeout(() => {
|
198 | session.post(cmd.stop, (err, data) => {
|
199 | if (err) return cb(null, { error: err.message || err })
|
200 | const profile = data.profile
|
201 |
|
202 | console.log(`Stopping ${cmd.stop}`)
|
203 | session.post(cmd.disable)
|
204 |
|
205 | fs.writeFile(msg.pwd, JSON.stringify(profile), (err) => {
|
206 | if (err) return cb(null, { error: err.message || err })
|
207 | return cb(null, { file : msg.pwd })
|
208 | })
|
209 | })
|
210 | }, timeout)
|
211 | })
|
212 | })
|
213 | }
|
214 |
|
215 | server.expose({
|
216 | killMe : that.close.bind(this),
|
217 | profileCPU : profile.bind(this, 'cpu'),
|
218 | profileMEM : profile.bind(this, 'mem'),
|
219 | prepare : God.prepare,
|
220 | getMonitorData : God.getMonitorData,
|
221 |
|
222 | startProcessId : God.startProcessId,
|
223 | stopProcessId : God.stopProcessId,
|
224 | restartProcessId : God.restartProcessId,
|
225 | deleteProcessId : God.deleteProcessId,
|
226 |
|
227 | sendLineToStdin : God.sendLineToStdin,
|
228 | softReloadProcessId : God.softReloadProcessId,
|
229 | reloadProcessId : God.reloadProcessId,
|
230 | duplicateProcessId : God.duplicateProcessId,
|
231 | resetMetaProcessId : God.resetMetaProcessId,
|
232 | stopWatch : God.stopWatch,
|
233 | startWatch : God.startWatch,
|
234 | toggleWatch : God.toggleWatch,
|
235 | notifyByProcessId : God.notifyByProcessId,
|
236 |
|
237 | notifyKillPM2 : God.notifyKillPM2,
|
238 | monitor : God.monitor,
|
239 | unmonitor : God.unmonitor,
|
240 |
|
241 | msgProcess : God.msgProcess,
|
242 | sendDataToProcessId : God.sendDataToProcessId,
|
243 | sendSignalToProcessId : God.sendSignalToProcessId,
|
244 | sendSignalToProcessName : God.sendSignalToProcessName,
|
245 |
|
246 | ping : God.ping,
|
247 | getVersion : God.getVersion,
|
248 | getReport : God.getReport,
|
249 | reloadLogs : God.reloadLogs
|
250 | });
|
251 |
|
252 | this.startLogic();
|
253 | }
|
254 |
|
255 | Daemon.prototype.close = function(opts, cb) {
|
256 | var that = this;
|
257 |
|
258 | God.bus.emit('pm2:kill', {
|
259 | status : 'killed',
|
260 | msg : 'pm2 has been killed via CLI'
|
261 | });
|
262 |
|
263 | if (God.system_infos_proc !== null)
|
264 | God.system_infos_proc.kill()
|
265 |
|
266 | |
267 |
|
268 |
|
269 | that.rpc_socket.close(function() {
|
270 | that.pub_socket.close(function() {
|
271 |
|
272 |
|
273 | if (cst.IS_WINDOWS === false) {
|
274 | try {
|
275 | process.kill(parseInt(opts.pid), 'SIGQUIT');
|
276 | } catch(e) {
|
277 | console.error('Could not send SIGQUIT to CLI');
|
278 | }
|
279 | }
|
280 |
|
281 | try {
|
282 | fs.unlinkSync(that.pid_path);
|
283 | } catch(e) {}
|
284 |
|
285 | console.log('PM2 successfully stopped');
|
286 | setTimeout(function() {
|
287 | process.exit(cst.SUCCESS_EXIT);
|
288 | }, 2);
|
289 | });
|
290 | });
|
291 | }
|
292 |
|
293 | Daemon.prototype.handleSignals = function() {
|
294 | var that = this;
|
295 |
|
296 | process.on('SIGTERM', that.gracefullExit.bind(this));
|
297 | process.on('SIGINT', that.gracefullExit.bind(this));
|
298 | process.on('SIGHUP', function() {});
|
299 | process.on('SIGQUIT', that.gracefullExit.bind(this));
|
300 | process.on('SIGUSR2', function() {
|
301 | God.reloadLogs({}, function() {});
|
302 | });
|
303 | }
|
304 |
|
305 | Daemon.prototype.sendReady = function(cb) {
|
306 |
|
307 | if (this.rpc_socket_ready == true && this.pub_socket_ready == true) {
|
308 | cb(null, {
|
309 | pid : process.pid,
|
310 | pm2_version : pkg.version
|
311 | });
|
312 | if (typeof(process.send) != 'function')
|
313 | return false;
|
314 |
|
315 | process.send({
|
316 | online : true,
|
317 | success : true,
|
318 | pid : process.pid,
|
319 | pm2_version : pkg.version
|
320 | });
|
321 | };
|
322 | }
|
323 |
|
324 | Daemon.prototype.gracefullExit = function() {
|
325 | var that = this;
|
326 |
|
327 |
|
328 |
|
329 | if (this.isExiting) return
|
330 |
|
331 | this.isExiting = true
|
332 |
|
333 | God.bus.emit('pm2:kill', {
|
334 | status : 'killed',
|
335 | msg : 'pm2 has been killed by SIGNAL'
|
336 | });
|
337 |
|
338 | console.log('pm2 has been killed by signal, dumping process list before exit...');
|
339 |
|
340 | if (God.system_infos_proc !== null)
|
341 | God.system_infos_proc.kill()
|
342 |
|
343 | God.dumpProcessList(function() {
|
344 |
|
345 | var processes = God.getFormatedProcesses();
|
346 |
|
347 | eachLimit(processes, 1, function(proc, next) {
|
348 | console.log('Deleting process %s', proc.pm2_env.pm_id);
|
349 | God.deleteProcessId(proc.pm2_env.pm_id, function() {
|
350 | return next();
|
351 | });
|
352 | }, function(err) {
|
353 | try {
|
354 | fs.unlinkSync(that.pid_path);
|
355 | } catch(e) {}
|
356 | setTimeout(function() {
|
357 | that.isExiting = false
|
358 | console.log('Exited peacefully');
|
359 | process.exit(cst.SUCCESS_EXIT);
|
360 | }, 2);
|
361 | });
|
362 | });
|
363 | }
|
364 |
|
365 | Daemon.prototype.startLogic = function() {
|
366 | var that = this;
|
367 |
|
368 | |
369 |
|
370 |
|
371 |
|
372 | God.bus.on('axm:action', function axmActions(msg) {
|
373 | var pm2_env = msg.process;
|
374 | var exists = false;
|
375 | var axm_action = msg.data;
|
376 |
|
377 | if (!pm2_env || !God.clusters_db[pm2_env.pm_id])
|
378 | return console.error('AXM ACTION Unknown id %s', pm2_env.pm_id);
|
379 |
|
380 | if (!God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions)
|
381 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions = [];
|
382 |
|
383 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.forEach(function(actions) {
|
384 | if (actions.action_name == axm_action.action_name)
|
385 | exists = true;
|
386 | });
|
387 |
|
388 | if (exists === false) {
|
389 | debug('Adding action', axm_action);
|
390 | God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(axm_action);
|
391 | }
|
392 | msg = null;
|
393 | });
|
394 |
|
395 | |
396 |
|
397 |
|
398 | God.bus.on('axm:option:configuration', function axmMonitor(msg) {
|
399 | if (!msg.process)
|
400 | return console.error('[axm:option:configuration] no process defined');
|
401 |
|
402 | if (!God.clusters_db[msg.process.pm_id])
|
403 | return console.error('[axm:option:configuration] Unknown id %s', msg.process.pm_id);
|
404 |
|
405 | try {
|
406 |
|
407 | if (msg.data.name)
|
408 | God.clusters_db[msg.process.pm_id].pm2_env.name = msg.data.name;
|
409 |
|
410 | Object.keys(msg.data).forEach(function(conf_key) {
|
411 | God.clusters_db[msg.process.pm_id].pm2_env.axm_options[conf_key] = Utility.clone(msg.data[conf_key]);
|
412 | });
|
413 | } catch(e) {
|
414 | console.error(e.stack || e);
|
415 | }
|
416 | msg = null;
|
417 | });
|
418 |
|
419 | |
420 |
|
421 |
|
422 | God.bus.on('axm:monitor', function axmMonitor(msg) {
|
423 | if (!msg.process)
|
424 | return console.error('[axm:monitor] no process defined');
|
425 |
|
426 | if (!msg.process || !God.clusters_db[msg.process.pm_id])
|
427 | return console.error('AXM MONITOR Unknown id %s', msg.process.pm_id);
|
428 |
|
429 | util._extend(God.clusters_db[msg.process.pm_id].pm2_env.axm_monitor, Utility.clone(msg.data));
|
430 | msg = null;
|
431 | });
|
432 |
|
433 | |
434 |
|
435 |
|
436 | God.bus.onAny(function(event, data_v) {
|
437 | if (['axm:action',
|
438 | 'axm:monitor',
|
439 | 'axm:option:setPID',
|
440 | 'axm:option:configuration'].indexOf(event) > -1) {
|
441 | data_v = null;
|
442 | return false;
|
443 | }
|
444 | that.pub.emit(event, Utility.clone(data_v));
|
445 | data_v = null;
|
446 | });
|
447 | };
|
448 |
|
449 | if (require.main === module) {
|
450 | process.title = process.env.PM2_DAEMON_TITLE || 'PM2 v' + pkg.version + ': God Daemon (' + process.env.PM2_HOME + ')';
|
451 |
|
452 | var daemon = new Daemon();
|
453 |
|
454 | daemon.start();
|
455 | }
|