UNPKG

12.9 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2021 the PM2 project authors. All rights reserved.
3 * Use of this source code is governed by a license that
4 * can be found in the LICENSE file.
5 */
6
7var debug = require('debug')('pm2:daemon');
8var pkg = require('../package.json');
9var cst = require('../constants.js');
10var rpc = require('pm2-axon-rpc');
11var axon = require('pm2-axon');
12var domain = require('domain');
13var Utility = require('./Utility.js');
14var util = require('util');
15var fs = require('fs');
16var God = require('./God');
17var eachLimit = require('async/eachLimit');
18var fmt = require('./tools/fmt.js');
19var semver = require('semver');
20
21var 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
34Daemon.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
67Daemon.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 // Write Daemon PID into file
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 * Pub system for real time notifications
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 * Rep/Req - RPC system to interact with God
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 * Memory Snapshot
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
255Daemon.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 * Cleanly kill pm2
268 */
269 that.rpc_socket.close(function() {
270 that.pub_socket.close(function() {
271
272 // notify cli that the daemon is shuting down (only under unix since windows doesnt handle signals)
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
293Daemon.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
305Daemon.prototype.sendReady = function(cb) {
306 // Send ready message to Client
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
324Daemon.prototype.gracefullExit = function() {
325 var that = this;
326
327 // never execute multiple gracefullExit simultaneously
328 // this can lead to loss of some apps in dump file
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
365Daemon.prototype.startLogic = function() {
366 var that = this;
367
368 /**
369 * Action treatment specifics
370 * Attach actions to pm2_env.axm_actions variables (name + options)
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 * Configure module
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 // Application Name nverride
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 * Process monitoring data (probes)
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 * Broadcast messages
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
449if (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}