Daemon.js 13 KB

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