Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50654 views
1
/*
2
* monitor.js: Core functionality for the Monitor object.
3
*
4
* (C) 2010 Nodejitsu Inc.
5
* MIT LICENCE
6
*
7
*/
8
9
var events = require('events'),
10
fs = require('fs'),
11
path = require('path'),
12
child_process = require('child_process'),
13
spawn = child_process.spawn,
14
broadway = require('broadway'),
15
psTree = require('ps-tree'),
16
utile = require('utile'),
17
common = require('./common'),
18
plugins = require('./plugins');
19
20
//
21
// ### function Monitor (script, options)
22
// #### @script {string} Location of the target script to run.
23
// #### @options {Object} Configuration for this instance.
24
// Creates a new instance of forever with specified `options`.
25
//
26
var Monitor = exports.Monitor = function (script, options) {
27
//
28
// Simple bootstrapper for attaching logger
29
// and watch plugins by default. Other plugins
30
// can be attached through `monitor.use(plugin, options)`.
31
//
32
function bootstrap(monitor) {
33
plugins.logger.attach.call(monitor, options);
34
if (options.watch) {
35
plugins.watch.attach.call(monitor, options);
36
}
37
}
38
39
var self = this;
40
41
//
42
// Setup basic configuration options
43
//
44
options = options || {};
45
this.silent = options.silent || false;
46
this.killTree = options.killTree !== false;
47
this.uid = options.uid || utile.randomString(4);
48
this.pidFile = options.pidFile;
49
this.max = options.max;
50
this.killTTL = options.killTTL;
51
this.childExists = false;
52
this.checkFile = options.checkFile !== false;
53
this.times = 0;
54
this.warn = console.error;
55
56
this.logFile = options.logFile;
57
this.outFile = options.outFile;
58
this.errFile = options.errFile;
59
this.append = options.append;
60
61
//
62
// Setup restart timing. These options control how quickly forever restarts
63
// a child process as well as when to kill a "spinning" process
64
//
65
this.minUptime = typeof options.minUptime !== 'number' ? 0 : options.minUptime;
66
this.spinSleepTime = options.spinSleepTime || null;
67
68
//
69
// Setup the command to spawn and the options to pass
70
// to that command.
71
//
72
this.command = options.command || process.execPath;
73
this.args = options.options || [];
74
this.spawnWith = options.spawnWith || {};
75
this.sourceDir = options.sourceDir;
76
this.fork = options.fork || false;
77
this.cwd = options.cwd || null;
78
this.hideEnv = options.hideEnv || [];
79
this._env = options.env || {};
80
this._hideEnv = {};
81
82
//
83
// Setup watch configuration options
84
//
85
this.watchIgnoreDotFiles = options.watchIgnoreDotFiles || true;
86
this.watchIgnorePatterns = options.watchIgnorePatterns || [];
87
this.watchDirectory = options.watchDirectory || this.sourceDir;
88
89
//
90
// Create a simple mapping of `this.hideEnv` to an easily indexable
91
// object
92
//
93
this.hideEnv.forEach(function (key) {
94
self._hideEnv[key] = true;
95
});
96
97
if (Array.isArray(script)) {
98
this.command = script[0];
99
this.args = script.slice(1);
100
}
101
else {
102
this.args.unshift(script);
103
}
104
105
if (this.sourceDir) {
106
this.args[0] = path.join(this.sourceDir, this.args[0]);
107
}
108
109
//
110
// Bootstrap this instance now that options
111
// have been set
112
//
113
broadway.App.call(this, { bootstrapper: { bootstrap: bootstrap } });
114
};
115
116
// Inherit from events.EventEmitter
117
utile.inherits(Monitor, broadway.App);
118
119
//
120
// ### function start ([restart])
121
// #### @restart {boolean} Value indicating whether this is a restart.
122
// Start the process that this instance is configured for
123
//
124
Monitor.prototype.start = function (restart) {
125
var self = this,
126
child;
127
128
if (this.running && !restart) {
129
process.nextTick(function () {
130
self.emit('error', new Error('Cannot start process that is already running.'));
131
});
132
return this;
133
}
134
135
child = this.trySpawn();
136
if (!child) {
137
process.nextTick(function () {
138
self.emit('error', new Error('Target script does not exist: ' + self.args[0]));
139
});
140
return this;
141
}
142
143
this.ctime = Date.now();
144
this.child = child;
145
this.running = true;
146
process.nextTick(function () {
147
self.emit(restart ? 'restart' : 'start', self, self.data);
148
});
149
150
function onMessage(msg) {
151
self.emit('message', msg);
152
}
153
154
// Re-emit messages from the child process
155
this.child.on('message', onMessage);
156
157
child.on('exit', function (code) {
158
var spinning = Date.now() - self.ctime < self.minUptime;
159
child.removeListener('message', onMessage);
160
self.emit('exit:code', code);
161
162
function letChildDie() {
163
self.running = false;
164
self.forceStop = false;
165
self.emit('exit', self, spinning);
166
}
167
168
function restartChild() {
169
self.forceRestart = false;
170
process.nextTick(function () {
171
self.start(true);
172
});
173
}
174
175
self.times++;
176
177
if (self.forceStop || (self.times >= self.max)
178
|| (spinning && typeof self.spinSleepTime !== 'number') && !self.forceRestart) {
179
letChildDie();
180
}
181
else if (spinning) {
182
setTimeout(restartChild, self.spinSleepTime);
183
}
184
else {
185
restartChild();
186
}
187
});
188
189
return this;
190
};
191
192
//
193
// ### function trySpawn()
194
// Tries to spawn the target Forever child process. Depending on
195
// configuration, it checks the first argument of the options
196
// to see if the file exists. This is useful is you are
197
// trying to execute a script with an env: e.g. node myfile.js
198
//
199
Monitor.prototype.trySpawn = function () {
200
if (/node/.test(this.command) && this.checkFile && !this.childExists) {
201
try {
202
var stats = fs.statSync(this.args[0]);
203
this.childExists = true;
204
}
205
catch (ex) {
206
return false;
207
}
208
}
209
210
this.spawnWith.cwd = this.cwd || this.spawnWith.cwd;
211
this.spawnWith.env = this._getEnv();
212
213
if (this.fork) {
214
this.spawnWith.stdio = [ 'pipe', 'pipe', 'pipe', 'ipc' ];
215
return spawn(this.command, this.args, this.spawnWith);
216
}
217
218
return spawn(this.command, this.args, this.spawnWith);
219
};
220
221
//
222
// ### @data {Object}
223
// Responds with the appropriate information about
224
// this `Monitor` instance and it's associated child process.
225
//
226
Monitor.prototype.__defineGetter__('data', function () {
227
var self = this,
228
childData;
229
230
if (!this.running) {
231
//
232
// TODO: Return settings from this forever instance
233
// with a state indicator that it is currently stopped.
234
//
235
return {};
236
}
237
238
childData = {
239
ctime: this.ctime,
240
command: this.command,
241
file: this.args[0],
242
foreverPid: process.pid,
243
logFile: this.logFile,
244
options: this.args.slice(1),
245
pid: this.child.pid,
246
silent: this.silent,
247
uid: this.uid,
248
spawnWith: this.spawnWith
249
};
250
251
['pidFile', 'outFile', 'errFile', 'env', 'cwd'].forEach(function (key) {
252
if (self[key]) {
253
childData[key] = self[key];
254
}
255
});
256
257
if (this.sourceDir) {
258
childData.sourceDir = this.sourceDir;
259
childData.file = childData.file.replace(this.sourceDir + '/', '');
260
}
261
262
this.childData = childData;
263
return this.childData;
264
265
//
266
// Setup the forever process to listen to
267
// SIGINT and SIGTERM events so that we can
268
// clean up the *.pid file
269
//
270
// Remark: This should work, but the fd gets screwed up
271
// with the daemon process.
272
//
273
// process.on('SIGINT', function () {
274
// process.exit(0);
275
// });
276
//
277
// process.on('SIGTERM', function () {
278
// process.exit(0);
279
// });
280
// process.on('exit', function () {
281
// fs.unlinkSync(childPath);
282
// });
283
});
284
285
//
286
// ### function restart ()
287
// Restarts the target script associated with this instance.
288
//
289
Monitor.prototype.restart = function () {
290
this.forceRestart = true;
291
return this.kill(false);
292
};
293
294
//
295
// ### function stop ()
296
// Stops the target script associated with this instance. Prevents it from auto-respawning
297
//
298
Monitor.prototype.stop = function () {
299
return this.kill(true);
300
};
301
302
//
303
// ### function kill (forceStop)
304
// #### @forceStop {boolean} Value indicating whether short circuit forever auto-restart.
305
// Kills the ChildProcess object associated with this instance.
306
//
307
Monitor.prototype.kill = function (forceStop) {
308
var self = this,
309
child = this.child;
310
311
if (!child || !this.running) {
312
process.nextTick(function () {
313
self.emit('error', new Error('Cannot stop process that is not running.'));
314
});
315
}
316
else {
317
//
318
// Set an instance variable here to indicate this
319
// stoppage is forced so that when `child.on('exit', ..)`
320
// fires in `Monitor.prototype.start` we can short circuit
321
// and prevent auto-restart
322
//
323
var toKill = [this.child.pid];
324
if (forceStop) {
325
this.forceStop = true;
326
//
327
// If we have a time before we truly kill forcefully, set up a timer
328
//
329
if (this.killTTL) {
330
var timer = setTimeout(function () {
331
toKill.forEach(function (pid) {
332
try {
333
process.kill(pid, 'SIGKILL');
334
}
335
catch (e) {
336
//conditions for races may exist, this is most likely an ESRCH
337
//these should be ignored, and then we should emit that it is dead
338
}
339
});
340
341
self.emit('stop', this.childData);
342
}, this.killTTL);
343
344
child.on('exit', function () {
345
clearTimeout(timer);
346
});
347
}
348
}
349
350
common.kill(this.child.pid, this.killTree, function () {
351
self.emit('stop', self.childData);
352
});
353
}
354
355
return this;
356
};
357
358
//
359
// ### @private function _getEnv ()
360
// Returns the environment variables that should be passed along
361
// to the target process spawned by this instance.
362
//
363
Monitor.prototype._getEnv = function () {
364
var self = this,
365
merged = {};
366
367
function addKey(key, source) {
368
merged[key] = source[key];
369
}
370
371
//
372
// Mixin the key:value pairs from `process.env` and the custom
373
// environment variables in `this._env`.
374
//
375
Object.keys(process.env).forEach(function (key) {
376
if (!self._hideEnv[key]) {
377
addKey(key, process.env);
378
}
379
});
380
381
Object.keys(this._env).forEach(function (key) {
382
addKey(key, self._env);
383
});
384
385
return merged;
386
};
387
388