master.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. 'use strict';
  2. const os = require('os');
  3. const v8 = require('v8');
  4. const util = require('util');
  5. const path = require('path');
  6. const fs = require('fs');
  7. const cluster = require('cluster');
  8. const EventEmitter = require('events');
  9. const childprocess = require('child_process');
  10. const cfork = require('cfork');
  11. const ready = require('get-ready');
  12. const GetFreePort = require('detect-port');
  13. const ConsoleLogger = require('egg-logger').EggConsoleLogger;
  14. const utility = require('utility');
  15. const semver = require('semver');
  16. const co = require('co');
  17. const { mkdirp } = require('mz-modules');
  18. const Manager = require('./utils/manager');
  19. const parseOptions = require('./utils/options');
  20. const Messenger = require('./utils/messenger');
  21. const terminate = require('./utils/terminate');
  22. const PROTOCOL = Symbol('Master#protocol');
  23. const REAL_PORT = Symbol('Master#real_port');
  24. const APP_ADDRESS = Symbol('Master#appAddress');
  25. class Master extends EventEmitter {
  26. /**
  27. * @class
  28. * @param {Object} options
  29. * - {String} [framework] - specify framework that can be absolute path or npm package
  30. * - {String} [baseDir] directory of application, default to `process.cwd()`
  31. * - {Object} [plugins] - customized plugins, for unittest
  32. * - {Number} [workers] numbers of app workers, default to `os.cpus().length`
  33. * - {Number} [port] listening port, default to 7001(http) or 8443(https)
  34. * - {Object} [https] https options, { key, cert, ca }, full path
  35. * - {Array|String} [require] will inject into worker/agent process
  36. * - {String} [pidFile] will save master pid to this file
  37. */
  38. constructor(options) {
  39. super();
  40. this.options = parseOptions(options);
  41. this.workerManager = new Manager();
  42. this.messenger = new Messenger(this);
  43. ready.mixin(this);
  44. this.isProduction = isProduction();
  45. this.agentWorkerIndex = 0;
  46. this.closed = false;
  47. this[REAL_PORT] = this.options.port;
  48. this[PROTOCOL] = this.options.https ? 'https' : 'http';
  49. // app started or not
  50. this.isStarted = false;
  51. this.logger = new ConsoleLogger({ level: process.env.EGG_MASTER_LOGGER_LEVEL || 'INFO' });
  52. this.logMethod = 'info';
  53. if (process.env.EGG_SERVER_ENV === 'local' || process.env.NODE_ENV === 'development') {
  54. this.logMethod = 'debug';
  55. }
  56. // get the real framework info
  57. const frameworkPath = this.options.framework;
  58. const frameworkPkg = utility.readJSONSync(path.join(frameworkPath, 'package.json'));
  59. this.log(`[master] =================== ${frameworkPkg.name} start =====================`);
  60. this.logger.info(`[master] node version ${process.version}`);
  61. /* istanbul ignore next */
  62. if (process.alinode) this.logger.info(`[master] alinode version ${process.alinode}`);
  63. this.logger.info(`[master] ${frameworkPkg.name} version ${frameworkPkg.version}`);
  64. if (this.isProduction) {
  65. this.logger.info('[master] start with options:%s%s',
  66. os.EOL, JSON.stringify(this.options, null, 2));
  67. } else {
  68. this.log('[master] start with options: %j', this.options);
  69. }
  70. this.log('[master] start with env: isProduction: %s, EGG_SERVER_ENV: %s, NODE_ENV: %s',
  71. this.isProduction, process.env.EGG_SERVER_ENV, process.env.NODE_ENV);
  72. const startTime = Date.now();
  73. this.ready(() => {
  74. this.isStarted = true;
  75. const stickyMsg = this.options.sticky ? ' with STICKY MODE!' : '';
  76. this.logger.info('[master] %s started on %s (%sms)%s',
  77. frameworkPkg.name, this[APP_ADDRESS], Date.now() - startTime, stickyMsg);
  78. const action = 'egg-ready';
  79. this.messenger.send({
  80. action,
  81. to: 'parent',
  82. data: {
  83. port: this[REAL_PORT],
  84. address: this[APP_ADDRESS],
  85. protocol: this[PROTOCOL],
  86. },
  87. });
  88. this.messenger.send({
  89. action,
  90. to: 'app',
  91. data: this.options,
  92. });
  93. this.messenger.send({
  94. action,
  95. to: 'agent',
  96. data: this.options,
  97. });
  98. // start check agent and worker status
  99. if (this.isProduction) {
  100. this.workerManager.startCheck();
  101. }
  102. });
  103. this.on('agent-exit', this.onAgentExit.bind(this));
  104. this.on('agent-start', this.onAgentStart.bind(this));
  105. this.on('app-exit', this.onAppExit.bind(this));
  106. this.on('app-start', this.onAppStart.bind(this));
  107. this.on('reload-worker', this.onReload.bind(this));
  108. // fork app workers after agent started
  109. this.once('agent-start', this.forkAppWorkers.bind(this));
  110. // get the real port from options and app.config
  111. // app worker will send after loading
  112. this.on('realport', ({ port, protocol }) => {
  113. if (port) this[REAL_PORT] = port;
  114. if (protocol) this[PROTOCOL] = protocol;
  115. });
  116. // https://nodejs.org/api/process.html#process_signal_events
  117. // https://en.wikipedia.org/wiki/Unix_signal
  118. // kill(2) Ctrl-C
  119. process.once('SIGINT', this.onSignal.bind(this, 'SIGINT'));
  120. // kill(3) Ctrl-\
  121. process.once('SIGQUIT', this.onSignal.bind(this, 'SIGQUIT'));
  122. // kill(15) default
  123. process.once('SIGTERM', this.onSignal.bind(this, 'SIGTERM'));
  124. process.once('exit', this.onExit.bind(this));
  125. // write pid to file if provided
  126. if (this.options.pidFile) {
  127. mkdirp.sync(path.dirname(this.options.pidFile));
  128. fs.writeFileSync(this.options.pidFile, process.pid.toString(), 'utf-8');
  129. }
  130. this.detectPorts()
  131. .then(() => {
  132. this.forkAgentWorker();
  133. });
  134. // exit when agent or worker exception
  135. this.workerManager.on('exception', ({
  136. agent,
  137. worker,
  138. }) => {
  139. const err = new Error(`[master] ${agent} agent and ${worker} worker(s) alive, exit to avoid unknown state`);
  140. err.name = 'ClusterWorkerExceptionError';
  141. err.count = {
  142. agent,
  143. worker,
  144. };
  145. this.logger.error(err);
  146. process.exit(1);
  147. });
  148. }
  149. detectPorts() {
  150. // Detect cluster client port
  151. return GetFreePort()
  152. .then(port => {
  153. this.options.clusterPort = port;
  154. // If sticky mode, detect worker port
  155. if (this.options.sticky) {
  156. return GetFreePort();
  157. }
  158. })
  159. .then(port => {
  160. if (this.options.sticky) {
  161. this.options.stickyWorkerPort = port;
  162. }
  163. })
  164. .catch(/* istanbul ignore next */ err => {
  165. this.logger.error(err);
  166. process.exit(1);
  167. });
  168. }
  169. log(...args) {
  170. this.logger[this.logMethod](...args);
  171. }
  172. get agentWorker() {
  173. return this.workerManager.agent;
  174. }
  175. startMasterSocketServer(cb) {
  176. // Create the outside facing server listening on our port.
  177. require('net').createServer({
  178. pauseOnConnect: true,
  179. }, connection => {
  180. // We received a connection and need to pass it to the appropriate
  181. // worker. Get the worker for this connection's source IP and pass
  182. // it the connection.
  183. /* istanbul ignore next */
  184. if (!connection.remoteAddress) {
  185. // This will happen when a client sends an RST(which is set to 1) right
  186. // after the three-way handshake to the server.
  187. // Read https://en.wikipedia.org/wiki/TCP_reset_attack for more details.
  188. connection.destroy();
  189. } else {
  190. const worker = this.stickyWorker(connection.remoteAddress);
  191. worker.send('sticky-session:connection', connection);
  192. }
  193. }).listen(this[REAL_PORT], cb);
  194. }
  195. stickyWorker(ip) {
  196. const workerNumbers = this.options.workers;
  197. const ws = this.workerManager.listWorkerIds();
  198. let s = '';
  199. for (let i = 0; i < ip.length; i++) {
  200. if (!isNaN(ip[i])) {
  201. s += ip[i];
  202. }
  203. }
  204. s = Number(s);
  205. const pid = ws[s % workerNumbers];
  206. return this.workerManager.getWorker(pid);
  207. }
  208. forkAgentWorker() {
  209. this.agentStartTime = Date.now();
  210. const args = [ JSON.stringify(this.options) ];
  211. const opt = {};
  212. if (process.platform === 'win32') opt.windowsHide = true;
  213. // add debug execArgv
  214. const debugPort = process.env.EGG_AGENT_DEBUG_PORT || 5800;
  215. if (this.options.isDebug) opt.execArgv = process.execArgv.concat([ `--${semver.gte(process.version, '8.0.0') ? 'inspect' : 'debug'}-port=${debugPort}` ]);
  216. const agentWorker = childprocess.fork(this.getAgentWorkerFile(), args, opt);
  217. agentWorker.status = 'starting';
  218. agentWorker.id = ++this.agentWorkerIndex;
  219. this.workerManager.setAgent(agentWorker);
  220. this.log('[master] agent_worker#%s:%s start with clusterPort:%s',
  221. agentWorker.id, agentWorker.pid, this.options.clusterPort);
  222. // send debug message
  223. if (this.options.isDebug) {
  224. this.messenger.send({
  225. to: 'parent',
  226. from: 'agent',
  227. action: 'debug',
  228. data: {
  229. debugPort,
  230. pid: agentWorker.pid,
  231. },
  232. });
  233. }
  234. // forwarding agent' message to messenger
  235. agentWorker.on('message', msg => {
  236. if (typeof msg === 'string') {
  237. msg = {
  238. action: msg,
  239. data: msg,
  240. };
  241. }
  242. msg.from = 'agent';
  243. this.messenger.send(msg);
  244. });
  245. agentWorker.on('error', err => {
  246. err.name = 'AgentWorkerError';
  247. err.id = agentWorker.id;
  248. err.pid = agentWorker.pid;
  249. this.logger.error(err);
  250. });
  251. // agent exit message
  252. agentWorker.once('exit', (code, signal) => {
  253. this.messenger.send({
  254. action: 'agent-exit',
  255. data: {
  256. code,
  257. signal,
  258. },
  259. to: 'master',
  260. from: 'agent',
  261. });
  262. });
  263. }
  264. forkAppWorkers() {
  265. this.appStartTime = Date.now();
  266. this.isAllAppWorkerStarted = false;
  267. this.startSuccessCount = 0;
  268. const args = [ JSON.stringify(this.options) ];
  269. this.log('[master] start appWorker with args %j', args);
  270. cfork({
  271. exec: this.getAppWorkerFile(),
  272. args,
  273. silent: false,
  274. count: this.options.workers,
  275. // don't refork in local env
  276. refork: this.isProduction,
  277. windowsHide: process.platform === 'win32',
  278. });
  279. let debugPort = process.debugPort;
  280. cluster.on('fork', worker => {
  281. worker.disableRefork = true;
  282. this.workerManager.setWorker(worker);
  283. worker.on('message', msg => {
  284. if (typeof msg === 'string') {
  285. msg = {
  286. action: msg,
  287. data: msg,
  288. };
  289. }
  290. msg.from = 'app';
  291. this.messenger.send(msg);
  292. });
  293. this.log('[master] app_worker#%s:%s start, state: %s, current workers: %j',
  294. worker.id, worker.process.pid, worker.state, Object.keys(cluster.workers));
  295. // send debug message, due to `brk` scence, send here instead of app_worker.js
  296. if (this.options.isDebug) {
  297. debugPort++;
  298. this.messenger.send({
  299. to: 'parent',
  300. from: 'app',
  301. action: 'debug',
  302. data: {
  303. debugPort,
  304. pid: worker.process.pid,
  305. },
  306. });
  307. }
  308. });
  309. cluster.on('disconnect', worker => {
  310. this.logger.info('[master] app_worker#%s:%s disconnect, suicide: %s, state: %s, current workers: %j',
  311. worker.id, worker.process.pid, worker.exitedAfterDisconnect, worker.state, Object.keys(cluster.workers));
  312. });
  313. cluster.on('exit', (worker, code, signal) => {
  314. this.messenger.send({
  315. action: 'app-exit',
  316. data: {
  317. workerPid: worker.process.pid,
  318. code,
  319. signal,
  320. },
  321. to: 'master',
  322. from: 'app',
  323. });
  324. });
  325. cluster.on('listening', (worker, address) => {
  326. this.messenger.send({
  327. action: 'app-start',
  328. data: {
  329. workerPid: worker.process.pid,
  330. address,
  331. },
  332. to: 'master',
  333. from: 'app',
  334. });
  335. });
  336. }
  337. /**
  338. * close agent worker, App Worker will closed by cluster
  339. *
  340. * https://www.exratione.com/2013/05/die-child-process-die/
  341. * make sure Agent Worker exit before master exit
  342. *
  343. * @param {number} timeout - kill agent timeout
  344. * @return {Promise} -
  345. */
  346. killAgentWorker(timeout) {
  347. const agentWorker = this.agentWorker;
  348. if (agentWorker) {
  349. this.log('[master] kill agent worker with signal SIGTERM');
  350. agentWorker.removeAllListeners();
  351. }
  352. return co(function* () {
  353. yield terminate(agentWorker, timeout);
  354. });
  355. }
  356. killAppWorkers(timeout) {
  357. return co(function* () {
  358. yield Object.keys(cluster.workers).map(id => {
  359. const worker = cluster.workers[id];
  360. worker.disableRefork = true;
  361. return terminate(worker, timeout);
  362. });
  363. });
  364. }
  365. /**
  366. * Agent Worker exit handler
  367. * Will exit during startup, and refork during running.
  368. * @param {Object} data
  369. * - {Number} code - exit code
  370. * - {String} signal - received signal
  371. */
  372. onAgentExit(data) {
  373. if (this.closed) return;
  374. this.messenger.send({
  375. action: 'egg-pids',
  376. to: 'app',
  377. data: [],
  378. });
  379. const agentWorker = this.agentWorker;
  380. this.workerManager.deleteAgent(this.agentWorker);
  381. const err = new Error(util.format('[master] agent_worker#%s:%s died (code: %s, signal: %s)',
  382. agentWorker.id, agentWorker.pid, data.code, data.signal));
  383. err.name = 'AgentWorkerDiedError';
  384. this.logger.error(err);
  385. // remove all listeners to avoid memory leak
  386. agentWorker.removeAllListeners();
  387. if (this.isStarted) {
  388. this.log('[master] try to start a new agent_worker after 1s ...');
  389. setTimeout(() => {
  390. this.logger.info('[master] new agent_worker starting...');
  391. this.forkAgentWorker();
  392. }, 1000);
  393. this.messenger.send({
  394. action: 'agent-worker-died',
  395. to: 'parent',
  396. });
  397. } else {
  398. this.logger.error('[master] agent_worker#%s:%s start fail, exiting with code:1',
  399. agentWorker.id, agentWorker.pid);
  400. process.exit(1);
  401. }
  402. }
  403. onAgentStart() {
  404. this.agentWorker.status = 'started';
  405. // Send egg-ready when agent is started after launched
  406. if (this.isAllAppWorkerStarted) {
  407. this.messenger.send({
  408. action: 'egg-ready',
  409. to: 'agent',
  410. data: this.options,
  411. });
  412. }
  413. this.messenger.send({
  414. action: 'egg-pids',
  415. to: 'app',
  416. data: [ this.agentWorker.pid ],
  417. });
  418. // should send current worker pids when agent restart
  419. if (this.isStarted) {
  420. this.messenger.send({
  421. action: 'egg-pids',
  422. to: 'agent',
  423. data: this.workerManager.getListeningWorkerIds(),
  424. });
  425. }
  426. this.messenger.send({
  427. action: 'agent-start',
  428. to: 'app',
  429. });
  430. this.logger.info('[master] agent_worker#%s:%s started (%sms)',
  431. this.agentWorker.id, this.agentWorker.pid, Date.now() - this.agentStartTime);
  432. }
  433. /**
  434. * App Worker exit handler
  435. * @param {Object} data
  436. * - {String} workerPid - worker id
  437. * - {Number} code - exit code
  438. * - {String} signal - received signal
  439. */
  440. onAppExit(data) {
  441. if (this.closed) return;
  442. const worker = this.workerManager.getWorker(data.workerPid);
  443. if (!worker.isDevReload) {
  444. const signal = data.signal;
  445. const message = util.format(
  446. '[master] app_worker#%s:%s died (code: %s, signal: %s, suicide: %s, state: %s), current workers: %j',
  447. worker.id, worker.process.pid, worker.process.exitCode, signal,
  448. worker.exitedAfterDisconnect, worker.state,
  449. Object.keys(cluster.workers)
  450. );
  451. if (this.options.isDebug && signal === 'SIGKILL') {
  452. // exit if died during debug
  453. this.logger.error(message);
  454. this.logger.error('[master] worker kill by debugger, exiting...');
  455. setTimeout(() => this.close(), 10);
  456. } else {
  457. const err = new Error(message);
  458. err.name = 'AppWorkerDiedError';
  459. this.logger.error(err);
  460. }
  461. }
  462. // remove all listeners to avoid memory leak
  463. worker.removeAllListeners();
  464. this.workerManager.deleteWorker(data.workerPid);
  465. // send message to agent with alive workers
  466. this.messenger.send({
  467. action: 'egg-pids',
  468. to: 'agent',
  469. data: this.workerManager.getListeningWorkerIds(),
  470. });
  471. if (this.isAllAppWorkerStarted) {
  472. // cfork will only refork at production mode
  473. this.messenger.send({
  474. action: 'app-worker-died',
  475. to: 'parent',
  476. });
  477. } else {
  478. // exit if died during startup
  479. this.logger.error('[master] app_worker#%s:%s start fail, exiting with code:1',
  480. worker.id, worker.process.pid);
  481. process.exit(1);
  482. }
  483. }
  484. /**
  485. * after app worker
  486. * @param {Object} data
  487. * - {String} workerPid - worker id
  488. * - {Object} address - server address
  489. */
  490. onAppStart(data) {
  491. const worker = this.workerManager.getWorker(data.workerPid);
  492. const address = data.address;
  493. // worker should listen stickyWorkerPort when sticky mode
  494. if (this.options.sticky) {
  495. if (String(address.port) !== String(this.options.stickyWorkerPort)) {
  496. return;
  497. }
  498. // worker should listen REALPORT when not sticky mode
  499. } else if (!isUnixSock(address) &&
  500. (String(address.port) !== String(this[REAL_PORT]))) {
  501. return;
  502. }
  503. // send message to agent with alive workers
  504. this.messenger.send({
  505. action: 'egg-pids',
  506. to: 'agent',
  507. data: this.workerManager.getListeningWorkerIds(),
  508. });
  509. this.startSuccessCount++;
  510. const remain = this.isAllAppWorkerStarted ? 0 : this.options.workers - this.startSuccessCount;
  511. this.log('[master] app_worker#%s:%s started at %s, remain %s (%sms)',
  512. worker.id, data.workerPid, address.port, remain, Date.now() - this.appStartTime);
  513. // Send egg-ready when app is started after launched
  514. if (this.isAllAppWorkerStarted) {
  515. this.messenger.send({
  516. action: 'egg-ready',
  517. to: 'app',
  518. data: this.options,
  519. });
  520. }
  521. // if app is started, it should enable this worker
  522. if (this.isAllAppWorkerStarted) {
  523. worker.disableRefork = false;
  524. }
  525. if (this.isAllAppWorkerStarted || this.startSuccessCount < this.options.workers) {
  526. return;
  527. }
  528. this.isAllAppWorkerStarted = true;
  529. // enable all workers when app started
  530. for (const id in cluster.workers) {
  531. const worker = cluster.workers[id];
  532. worker.disableRefork = false;
  533. }
  534. address.protocol = this[PROTOCOL];
  535. address.port = this.options.sticky ? this[REAL_PORT] : address.port;
  536. this[APP_ADDRESS] = getAddress(address);
  537. if (this.options.sticky) {
  538. this.startMasterSocketServer(err => {
  539. if (err) return this.ready(err);
  540. this.ready(true);
  541. });
  542. } else {
  543. this.ready(true);
  544. }
  545. }
  546. /**
  547. * master exit handler
  548. */
  549. onExit(code) {
  550. if (this.options.pidFile && fs.existsSync(this.options.pidFile)) {
  551. try {
  552. fs.unlinkSync(this.options.pidFile);
  553. } catch (err) {
  554. /* istanbul ignore next */
  555. this.logger.error('[master] delete pidfile %s fail with %s', this.options.pidFile, err.message);
  556. }
  557. }
  558. // istanbul can't cover here
  559. // https://github.com/gotwarlost/istanbul/issues/567
  560. const level = code === 0 ? 'info' : 'error';
  561. this.logger[level]('[master] exit with code:%s', code);
  562. }
  563. onSignal(signal) {
  564. if (this.closed) return;
  565. this.logger.info('[master] master is killed by signal %s, closing', signal);
  566. // logger more info
  567. const { used_heap_size, heap_size_limit } = v8.getHeapStatistics();
  568. this.logger.info('[master] system memory: total %s, free %s', os.totalmem(), os.freemem());
  569. this.logger.info('[master] process info: heap_limit %s, heap_used %s', heap_size_limit, used_heap_size);
  570. this.close();
  571. }
  572. /**
  573. * reload workers, for develop purpose
  574. */
  575. onReload() {
  576. this.log('[master] reload workers...');
  577. for (const id in cluster.workers) {
  578. const worker = cluster.workers[id];
  579. worker.isDevReload = true;
  580. }
  581. require('cluster-reload')(this.options.workers);
  582. }
  583. close() {
  584. this.closed = true;
  585. const self = this;
  586. co(function* () {
  587. try {
  588. yield self._doClose();
  589. self.log('[master] close done, exiting with code:0');
  590. process.exit(0);
  591. } catch (e) /* istanbul ignore next */ {
  592. this.logger.error('[master] close with error: ', e);
  593. process.exit(1);
  594. }
  595. });
  596. }
  597. getAgentWorkerFile() {
  598. return path.join(__dirname, 'agent_worker.js');
  599. }
  600. getAppWorkerFile() {
  601. return path.join(__dirname, 'app_worker.js');
  602. }
  603. * _doClose() {
  604. // kill app workers
  605. // kill agent worker
  606. // exit itself
  607. const legacyTimeout = process.env.EGG_MASTER_CLOSE_TIMEOUT || 5000;
  608. const appTimeout = process.env.EGG_APP_CLOSE_TIMEOUT || legacyTimeout;
  609. const agentTimeout = process.env.EGG_AGENT_CLOSE_TIMEOUT || legacyTimeout;
  610. this.logger.info('[master] send kill SIGTERM to app workers, will exit with code:0 after %sms', appTimeout);
  611. this.logger.info('[master] wait %sms', appTimeout);
  612. try {
  613. yield this.killAppWorkers(appTimeout);
  614. } catch (e) /* istanbul ignore next */ {
  615. this.logger.error('[master] app workers exit error: ', e);
  616. }
  617. this.logger.info('[master] send kill SIGTERM to agent worker, will exit with code:0 after %sms', agentTimeout);
  618. this.logger.info('[master] wait %sms', agentTimeout);
  619. try {
  620. yield this.killAgentWorker(agentTimeout);
  621. } catch (e) /* istanbul ignore next */ {
  622. this.logger.error('[master] agent worker exit error: ', e);
  623. }
  624. }
  625. }
  626. module.exports = Master;
  627. function isProduction() {
  628. const serverEnv = process.env.EGG_SERVER_ENV;
  629. if (serverEnv) {
  630. return serverEnv !== 'local' && serverEnv !== 'unittest';
  631. }
  632. return process.env.NODE_ENV === 'production';
  633. }
  634. function getAddress({
  635. addressType,
  636. address,
  637. port,
  638. protocol,
  639. }) {
  640. // unix sock
  641. // https://nodejs.org/api/cluster.html#cluster_event_listening_1
  642. if (addressType === -1) return address;
  643. let hostname = address;
  644. if (!hostname && process.env.HOST && process.env.HOST !== '0.0.0.0') {
  645. hostname = process.env.HOST;
  646. }
  647. if (!hostname) {
  648. hostname = '127.0.0.1';
  649. }
  650. return `${protocol}://${hostname}:${port}`;
  651. }
  652. function isUnixSock(address) {
  653. return address.addressType === -1;
  654. }