UNPKG

12.7 kBJavaScriptView Raw
1/**
2 * Copyright 2013 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 ? 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
66Daemon.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 // Write Daemon PID into file
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 * Pub system for real time notifications
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 * Rep/Req - RPC system to interact with God
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 * Memory Snapshot
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
254Daemon.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 * Cleanly kill pm2
264 */
265 that.rpc_socket.close(function() {
266 that.pub_socket.close(function() {
267
268 // notify cli that the daemon is shuting down (only under unix since windows doesnt handle signals)
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
289Daemon.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
301Daemon.prototype.sendReady = function(cb) {
302 // Send ready message to Client
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
320Daemon.prototype.gracefullExit = function() {
321 var that = this;
322
323 // never execute multiple gracefullExit simultaneously
324 // this can lead to loss of some apps in dump file
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
358Daemon.prototype.startLogic = function() {
359 var that = this;
360
361 /**
362 * Action treatment specifics
363 * Attach actions to pm2_env.axm_actions variables (name + options)
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 * Configure module
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 // Application Name nverride
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 * Process monitoring data (probes)
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 * Broadcast messages
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
442if (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}