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