UNPKG

18 kBJavaScriptView Raw
1
2////////////////////////////////////////////////////////////////////////
3// /!\ //
4// THIS FILE IS NOT USED ANYMORE IT IS ONLY HERE FOR BACKWARD SUPPORT //
5// WHILE UPGRADING OLDER PM2 (< 2.0) //
6// //
7// THIS FILE HAS BEEN NOW SPLITTED INTO TWO DISTINCT FILES //
8// //
9// NAMED //
10// //
11// CLIENT.JS FOR THE CLIENT SIDE //
12// AND //
13// DAEMON.JS FOR THE DAEMON (SERVER SIDE) //
14// /!\ //
15////////////////////////////////////////////////////////////////////////
16
17var cst = require('../constants.js');
18var rpc = require('pm2-axon-rpc');
19var axon = require('pm2-axon');
20var debug = require('debug')('pm2:satan');
21var util = require('util');
22var fs = require('fs');
23var p = require('path');
24var Utility = require('./Utility.js');
25var domain = require('domain');
26var eachLimit = require('async/eachLimit');
27
28/**
29 * Export
30 */
31var Satan = module.exports = {};
32
33/**
34 * This function ensures that daemon is running and start it if it doesn't
35 * Then connect to PM2 via RPC
36 * @api public
37 * @method start
38 * @param {Boolean} noDaemonMode option to not fork PM2 and run it in the same process
39 * @callback cb
40 */
41Satan.start = function(noDaemonMode, cb) {
42 if (typeof(noDaemonMode) == "function") {
43 cb = noDaemonMode;
44 noDaemonMode = false;
45 }
46
47 Satan._noDaemonMode = noDaemonMode;
48
49 Satan.pingDaemon(function(ab) {
50 // If Daemon not alive
51 if (ab == false) {
52 if (noDaemonMode) {
53 debug('Launching in no daemon mode');
54 Satan.remoteWrapper();
55 return Satan.launchRPC(function() {
56 require('./Modularizer.js').launchAll(cb);
57 });
58 }
59
60 Satan.printOut(cst.PREFIX_MSG + 'Spawning PM2 daemon');
61
62 // Daemonize PM2
63 return Satan.launchDaemon(function(err, child) {
64 if (err) {
65 console.error(err);
66 return cb ? cb(err) : process.exit(cst.ERROR_EXIT);
67 }
68 Satan.printOut(cst.PREFIX_MSG + 'PM2 Successfully daemonized');
69 // Launch RPC
70 return Satan.launchRPC(function() {
71 require('./Modularizer.js').launchAll(cb);
72 });
73 });
74 }
75 // Else just start the PM2 client side (RPC)
76 return Satan.launchRPC(cb);
77 });
78};
79
80/**
81 * Daemon part
82 * @method processStateHandler
83 * @param {} God
84 * @return
85 */
86Satan.processStateHandler = function(God) {
87 /**
88 * Description
89 * @method gracefullExit
90 * @return
91 */
92 function gracefullExit() {
93 Satan.printOut('pm2 has been killed by signal, dumping process list before exit...');
94
95 God.dumpProcessList(function() {
96
97 var processes = God.getFormatedProcesses();
98
99 eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) {
100 console.log('Deleting process %s', proc.pm2_env.pm_id);
101 God.deleteProcessId(proc.pm2_env.pm_id, function() {
102 return next();
103 });
104 return false;
105 }, function(err) {
106 try {
107 fs.unlinkSync(cst.PM2_PID_FILE_PATH);
108 } catch(e) {}
109 Satan.printOut('[PM2] Exited peacefully');
110 process.exit(cst.SUCCESS_EXIT);
111 });
112 });
113 }
114
115 try {
116 fs.writeFileSync(cst.PM2_PID_FILE_PATH, process.pid);
117 } catch (e) {
118 console.error(e.stack || e);
119 }
120
121 process.on('SIGILL', function() {
122 global.gc();
123 Satan.printOut('Running garbage collector');
124 });
125
126 process.on('SIGTERM', gracefullExit);
127 process.on('SIGINT', gracefullExit);
128 process.on('SIGQUIT', gracefullExit);
129 process.on('SIGUSR2', function() {
130 God.reloadLogs({}, function() {});
131 });
132};
133
134/**
135 * This function wrap God.js
136 * @method remoteWrapper
137 * @return
138 */
139Satan.remoteWrapper = function() {
140 // Only require here because God init himself
141 var God = require('./God');
142 var self = this;
143
144 var pkg = require('../package.json');
145 var rpc_socket_ready = false;
146 var pub_socket_ready = false;
147
148 Satan.processStateHandler(God);
149
150 function sendReady() {
151 // Send ready message to Satan Client
152 if (rpc_socket_ready == true && pub_socket_ready == true) {
153 if (typeof(process.send) === 'function') {
154 process.send({
155 online : true,
156 success : true,
157 pid : process.pid,
158 pm2_version : pkg.version
159 });
160 }
161 };
162 }
163
164 /**
165 * External interaction part
166 */
167
168 /**
169 * Pub system for real time notifications
170 */
171 var pub = axon.socket('pub-emitter');
172
173 this.pub_socket = pub.bind(cst.DAEMON_PUB_PORT);
174
175 this.pub_socket.once('bind', function() {
176 Satan.printOut('BUS system [READY] on port %s', cst.DAEMON_PUB_PORT);
177 pub_socket_ready = true;
178 sendReady();
179 });
180
181 /**
182 * Rep/Req - RPC system to interact with God
183 */
184 var rep = axon.socket('rep');
185
186 var server = new rpc.Server(rep);
187
188 Satan.printOut('[[[[ PM2/God daemon launched ]]]]');
189
190 this.rpc_socket = rep.bind(cst.DAEMON_RPC_PORT);
191
192 this.rpc_socket.once('bind', function() {
193 Satan.printOut('RPC interface [READY] on port %s', cst.DAEMON_RPC_PORT);
194 rpc_socket_ready = true;
195 sendReady();
196 });
197
198 server.expose({
199 prepare : God.prepare,
200 getMonitorData : God.getMonitorData,
201 getSystemData : God.getSystemData,
202
203 startProcessId : God.startProcessId,
204 stopProcessId : God.stopProcessId,
205 restartProcessId : God.restartProcessId,
206 deleteProcessId : God.deleteProcessId,
207
208 softReloadProcessId : God.softReloadProcessId,
209 reloadProcessId : God.reloadProcessId,
210 duplicateProcessId : God.duplicateProcessId,
211 resetMetaProcessId : God.resetMetaProcessId,
212 stopWatch : God.stopWatch,
213 restartWatch : God.restartWatch,
214 notifyByProcessId : God.notifyByProcessId,
215
216 killMe : God.killMe,
217 notifyKillPM2 : God.notifyKillPM2,
218
219 findByFullPath : God.findByFullPath,
220
221 msgProcess : God.msgProcess,
222 sendDataToProcessId : God.sendDataToProcessId,
223 sendSignalToProcessId : God.sendSignalToProcessId,
224 sendSignalToProcessName : God.sendSignalToProcessName,
225
226 ping : God.ping,
227 getVersion : God.getVersion,
228 reloadLogs : God.reloadLogs
229 });
230
231 /**
232 * Action treatment specifics
233 * Attach actions to pm2_env.axm_actions variables (name + options)
234 */
235 God.bus.on('axm:action', function axmActions(msg) {
236 var pm2_env = msg.process;
237 var exists = false;
238 var axm_action = msg.data;
239
240 if (!pm2_env || !God.clusters_db[pm2_env.pm_id])
241 return console.error('Unknown id %s', pm2_env.pm_id);
242
243 if (!God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions)
244 God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions = [];
245
246 God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.forEach(function(actions) {
247 if (actions.action_name == axm_action.action_name)
248 exists = true;
249 });
250
251 if (exists === false) {
252 debug('Adding action', axm_action);
253 God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(axm_action);
254 }
255
256 return God;
257 });
258
259 /**
260 * Configure module
261 */
262 God.bus.on('axm:option:configuration', function axmMonitor(msg) {
263 if (!msg.process)
264 return console.error('[axm:option:configuration] no process defined');
265
266 if (!God.clusters_db[msg.process.pm_id])
267 return console.error('[axm:option:configuration] Unknown id %s', msg.process.pm_id);
268
269 try {
270 // Application Name nverride
271 if (msg.data.name)
272 God.clusters_db[msg.process.pm_id].pm2_env.name = msg.data.name;
273
274 Object.keys(msg.data).forEach(function(conf_key) {
275 God.clusters_db[msg.process.pm_id].pm2_env.axm_options[conf_key] = Utility.clone(msg.data[conf_key]);
276 });
277 } catch(e) {
278 console.error(e.stack || e);
279 }
280 msg = null;
281 });
282
283 /**
284 * Process monitoring data (probes)
285 */
286 God.bus.on('axm:monitor', function axmMonitor(msg) {
287 if (!msg.process)
288 return console.error('[axm:monitor] no process defined');
289
290 if (!msg.process || !God.clusters_db[msg.process.pm_id])
291 return console.error('Unknown id %s', msg.process.pm_id);
292
293 util._extend(God.clusters_db[msg.process.pm_id].pm2_env.axm_monitor, Utility.clone(msg.data));
294 msg = null;
295 });
296
297 /**
298 * Broadcast messages
299 */
300 God.bus.onAny(function(data_v) {
301 if (['axm:action',
302 'axm:monitor',
303 'axm:option:setPID',
304 'axm:option:configuration'].indexOf(this.event) > -1) {
305 data_v = null;
306 return false;
307 }
308 pub.emit(this.event, Utility.clone(data_v));
309 data_v = null;
310 });
311};
312
313/**
314 *
315 * Client part
316 *
317 */
318
319/**
320 * Launch the Daemon by forking this same file
321 * The method Satan.remoteWrapper will be called
322 * @api public
323 * @method launchDaemon
324 * @param {} cb
325 * @return
326 */
327Satan.launchDaemon = function launchDaemon(cb) {
328 debug('Launching daemon');
329
330 var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js');
331 var InteractorDaemonizer = require('@pm2/agent/src/InteractorClient');
332
333 var node_args = [];
334
335 var out, err;
336
337 if (process.env.TRAVIS) {
338 // Redirect PM2 internal err and out to STDERR STDOUT when running with Travis
339 out = 1;
340 err = 2;
341 }
342 else {
343 out = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a'),
344 err = fs.openSync(cst.PM2_LOG_FILE_PATH, 'a');
345 }
346
347 // Node.js tuning for better performance
348 //node_args.push('--expose-gc'); // Allows manual GC in the code
349 //node_args.push('--gc-global'); // Does full GC (smaller memory footprint)
350
351 /**
352 * Add node [arguments] depending on PM2_NODE_OPTIONS env variable
353 */
354 if (process.env.PM2_NODE_OPTIONS)
355 node_args = node_args.concat(process.env.PM2_NODE_OPTIONS.split(' '));
356 node_args.push(SatanJS);
357
358 var resolved_home = process.env.PM2_HOME || process.env.HOME || process.env.HOMEPATH;
359
360 debug("PM2 home path: %s", resolved_home);
361 debug("Node.js engine full path: %s", process.execPath);
362 debug("Node.js with V8 arguments: %s", node_args);
363
364 var child = require('child_process').spawn(process.execPath || 'node', node_args, {
365 detached : true,
366 cwd : process.cwd(),
367 env : util._extend({
368 'SILENT' : cst.DEBUG ? !cst.DEBUG : true,
369 'HOME' : resolved_home
370 }, process.env),
371 stdio : ['ipc', out, err]
372 });
373
374 function onError(e) {
375 console.error(e.stack || e);
376 return cb ? cb(e.stack || e) : false;
377 }
378
379 child.once('error', onError);
380
381 child.unref();
382
383 child.once('message', function(msg) {
384 debug('PM2 daemon launched with return message: ', msg);
385 child.removeListener('error', onError);
386 child.disconnect();
387 InteractorDaemonizer.launchAndInteract({}, {}, function(err, data) {
388 if (data)
389 debug('Interactor launched');
390 return cb ? cb(null, child) : false;
391 });
392 });
393};
394
395/**
396 * Ping the daemon to know if it alive or not
397 * @api public
398 * @method pingDaemon
399 * @param {} cb
400 * @return
401 */
402Satan.pingDaemon = function pingDaemon(cb) {
403 var req = axon.socket('req');
404 var client = new rpc.Client(req);
405
406 debug('[PING PM2] Trying to connect to server');
407
408 client.sock.once('reconnect attempt', function() {
409 client.sock.close();
410 debug('Daemon not launched');
411 process.nextTick(function() {
412 return cb(false);
413 });
414 });
415
416 client.sock.once('connect', function() {
417 client.sock.once('close', function() {
418 return cb(true);
419 });
420 client.sock.close();
421 debug('Daemon alive');
422 });
423
424 req.connect(cst.DAEMON_RPC_PORT);
425};
426
427/**
428 * Methods to interact with the Daemon via RPC
429 * This method wait to be connected to the Daemon
430 * Once he's connected it trigger the command parsing (on ./bin/pm2 file, at the end)
431 * @method launchRPC
432 * @return
433 */
434Satan.launchRPC = function launchRPC(cb) {
435 debug('Launching RPC client on socket file %s', cst.DAEMON_RPC_PORT);
436 var req = axon.socket('req');
437 Satan.client = new rpc.Client(req);
438
439 Satan.client.sock.once('connect', function() {
440 debug('Connected to Daemon');
441 process.emit('satan:client:ready');
442 setTimeout(function() {
443 return cb ? cb(null) : false;
444 }, 4);
445 });
446
447 this.client_sock = req.connect(cst.DAEMON_RPC_PORT);
448};
449
450Satan.launchBus = function launchEventSystem(cb) {
451 var self = this;
452 this.sub = axon.socket('sub-emitter');
453 this.sub_sock = this.sub.connect(cst.DAEMON_PUB_PORT);
454
455 this.sub_sock.once('connect', function() {
456 return cb(null, self.sub);
457 });
458};
459
460Satan.disconnectBus = function disconnectBus(cb) {
461 this.sub_sock.once('close', function() {
462 return cb ? cb() : false;
463 });
464 this.sub_sock.close();
465};
466
467/**
468 * Methods to close the RPC connection
469 * @callback cb
470 */
471Satan.disconnectRPC = function disconnectRPC(cb) {
472 debug('Disconnecting PM2 RPC');
473
474 if (!Satan.client_sock || !Satan.client_sock.close) {
475 return cb({
476 success : false,
477 msg : 'RPC connection to PM2 is not launched'
478 });
479 }
480
481 if (Satan.client_sock.connected == false ||
482 Satan.client_sock.closing == true) {
483 return cb({
484 success : false,
485 msg : 'RPC closed'
486 });
487 }
488
489 try {
490 var timer;
491
492 Satan.client_sock.once('close', function() {
493 clearTimeout(timer);
494 debug('PM2 RPC cleanly closed');
495 return cb ? cb(null, {success:true}) : false;
496 });
497
498 timer = setTimeout(function() {
499 if (Satan.client_sock.destroy)
500 Satan.client_sock.destroy();
501 return cb ? cb(null, {success:true}) : false;
502 }, 200);
503
504 Satan.client_sock.close();
505 } catch(e) {
506 debug('Error while disconnecting RPC PM2', e.stack || e);
507 return cb ? cb(e.stack || e) : false;
508 };
509 return false;
510};
511
512/**
513 * Description
514 * @method getExposedMethods
515 * @param {} cb
516 * @return
517 */
518Satan.getExposedMethods = function getExposedMethods(cb) {
519 Satan.client.methods(cb);
520};
521
522Satan.printOut = function() {
523 if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
524 return console.log.apply(console, arguments);
525};
526
527/**
528 * Description
529 * @method executeRemote
530 * @param {} method
531 * @param {} env
532 * @param {} fn
533 * @return
534 */
535Satan.executeRemote = function executeRemote(method, env, fn) {
536 var env_watch = false;
537
538 if (env.env && env.env.watch)
539 env_watch = env.env.watch;
540
541 env_watch = util.isArray(env_watch) && env_watch.length === 0 ? !!~process.argv.indexOf('--watch') : env_watch;
542
543 //stop watching when process is deleted
544 if (method.indexOf('delete') !== -1) {
545 Satan.stopWatch(method, env);
546 //stop everything on kill
547 } else if(method.indexOf('kill') !== -1) {
548 Satan.stopWatch('deleteAll', env);
549 //stop watch on stop (stop doesn't accept env, yet)
550 } else if (~process.argv.indexOf('--watch') && method.indexOf('stop') !== -1) {
551 Satan.stopWatch(method, env);
552 //restart watch
553 } else if (env_watch && method.indexOf('restart') !== -1) {
554 Satan.restartWatch(method, env);
555 }
556
557 if (!Satan.client || !Satan.client.call) {
558 if (fn) return fn(new Error('Could not connect to local pm2, have you called pm2.connect(function()})'));
559 console.error('Did you forgot to call pm2.connect(function() { }) before interacting with PM2 ?');
560 return process.exit(0);
561 }
562
563 debug('Calling daemon method pm2:%s', method);
564 return Satan.client.call(method, env, fn);
565};
566
567Satan.notifyGod = function(action_name, id, cb) {
568 Satan.executeRemote('notifyByProcessId', {
569 id : id,
570 action_name : action_name,
571 manually : true
572 }, function() {
573 debug('God notified');
574 return cb ? cb() : false;
575 });
576};
577/**
578 * Description
579 * @method killDaemon
580 * @param {} fn
581 * @return
582 */
583Satan.killDaemon = function killDaemon(fn) {
584 var timeout;
585
586 function quit() {
587 Satan.disconnectRPC(function() {
588 debug('RPC disconnected');
589 return fn ? fn(null, {success:true}) : false;
590 });
591 }
592
593 process.once('SIGQUIT', function() {
594 debug('Received SIGQUIT');
595 clearTimeout(timeout);
596 quit();
597 });
598
599 timeout = setTimeout(function() {
600 quit();
601 }, 3000);
602
603 // Kill daemon
604 Satan.executeRemote('killMe', {pid : process.pid}, function() {});
605};
606
607/**
608 * Description
609 * @method restartWatch
610 * @param {} method
611 * @param {} env
612 * @param {} fn
613 * @return
614 */
615Satan.restartWatch = function restartWatch(method, env, fn) {
616 debug('Calling restartWatch');
617 Satan.client.call('restartWatch', method, env, function() {
618 debug('Restart watching');
619 return fn ? fn() : false;
620 });
621};
622
623/**
624 * Description
625 * @method stopWatch
626 * @param {} method
627 * @param {} env
628 * @param {} fn
629 * @return
630 */
631Satan.stopWatch = function stopWatch(method, env, fn) {
632 debug('Calling stopWatch');
633 Satan.client.call('stopWatch', method, env, function() {
634 debug('Stop watching');
635 return fn ? fn() : false;
636 });
637};
638
639/**
640 * If this file is a main process, it means that
641 * this process is being forked by pm2 itself
642 */
643if (require.main === module) {
644
645 var pkg = require('../package.json');
646
647 process.title = 'PM2 v' + pkg.version + ': God Daemon';
648
649 if (process.env.NODE_ENV == 'test') {
650 Satan.remoteWrapper();
651 }
652 else {
653 var d = domain.create();
654
655 d.once('error', function(err) {
656 console.error('[PM2] Error caught by domain:\n' + (err.stack || err));
657 console.error('[PM2] Trying to update PM2...');
658
659 require('child_process').spawn('node', [process.env['_'], 'update'], {
660 detached: true,
661 stdio: 'inherit'
662 });
663
664 });
665
666 d.run(function() {
667 Satan.remoteWrapper();
668 });
669 }
670}