Permalink
Browse files

Process are now daemon by default, change detached property to attach

  • Loading branch information...
1 parent 268a5d4 commit 63995ee095b869fe6b4486fcec92c4ff87d5e53d @wdavidw committed Dec 4, 2011
View
80 README.md
@@ -114,7 +114,7 @@ The following events may be emitted:
- `"error"` , called on error providing the error object as the first callback argument.
- `"exit"` , called when the process exit.
-## `Request` parameter
+## Request parameter
The request object contains the following properties:
@@ -124,7 +124,7 @@ The request object contains the following properties:
- `qestion` , Ask questions with optionally suggested and default answers
- `confirm` , Ask a question expecting a boolean answer
-## `Response` parameter
+## Response parameter
The response object inherits from styles containing methods for printing, coloring and bolding:
@@ -150,14 +150,14 @@ Style:
Display:
-- `prompt` , Exits the current command and return user to the prompt.
-- `ln`
-- `print`
-- `println`
-- `constructor`
-- `reset`
-- `pad`
-- `raw`
+
+- `prompt` , Exits the current command and return user to the prompt.
+- `ln` , Print a new line
+- `print` , Print a text
+- `println` , Print a text followed by a new line
+- `reset` , Stop any formating like color or bold
+- `pad` , Print a text with a fixed padding
+- `raw` , Return a text
## Router plugin
@@ -219,15 +219,15 @@ app.cmd('user :id([0-9]+)', function(req, res) {
Persistent command history over multiple sessions. Options passed during creation are:
-- `shell` , (required) A reference to your shell application.
-- `name` , Identify your project history file, default to the hash of the exectuted file
-- `dir` , Location of the history files, defaults to `"#{process.env['HOME']}/.node_shell"`
+- `shell` , (required) A reference to your shell application.
+- `name` , Identify your project history file, default to the hash of the exectuted file
+- `dir` , Location of the history files, defaults to `"#{process.env['HOME']}/.node_shell"`
## Completer plugin
Provides tab completion. Options passed during creation are:
-- `shell` , (required) A reference to your shell application.
+- `shell` , (required) A reference to your shell application.
## Help plugin
@@ -243,8 +243,8 @@ Additionnaly, a new `shell.help()` function is made available. Options passed du
Register two commands, `http start` and `http stop`. The start command will search for "./server.js" and "./app.js" (and additionnaly their CoffeeScript alternatives) to run by `node`.The following properties may be provided as settings:
- `config` , Path to the configuration file. Required to launch redis.
-- `detach` , Wether the HTTP process should be attached to the current process. If not defined, default to `true` in shell mode and `false` in command mode.
-- `pidfile` , Path to the file storing the detached process id. Defaults to `"/.node_shell/#{md5}.pid"`
+- `attach` , Wether the HTTP process should be attached to the current process. If not defined, default to `false` (the server run as a daemon).
+- `pidfile` , Path to the file storing the daemon process id. Defaults to `"/.node_shell/#{md5}.pid"`
- `stdout` , Writable stream or file path to redirect the server stdout.
- `stderr` , Writable stream or file path to redirect the server stderr.
- `workspace`, Project directory used to resolve relative paths and search for "server" and "app" scripts.
@@ -271,11 +271,11 @@ app.configure(function() {
Register two commands, `redis start` and `redis stop`. The following properties may be provided as settings:
-- `config` Path to the configuration file. Required to launch redis.
-- `detach` Wether the Redis process should be attached to the current process. If not defined, default to `true` in shell mode and `false` in command mode.
-- `pidfile` Path to the file storing the detached process id. Defaults to `"/.node_shell/#{md5}.pid"`
-- `stdout` Writable stream or file path to redirect cloud9 stdout.
-- `stderr` Writable stream or file path to redirect cloud9 stderr.
+- `config` , Path to the configuration file. Required to launch redis.
+- `attach` , Wether the Redis process should be attached to the current process. If not defined, default to `false` (the server run as a daemon).
+- `pidfile` Path to the file storing the daemon process id. Defaults to `"/.node_shell/#{md5}.pid"`
+- `stdout` , Writable stream or file path to redirect cloud9 stdout.
+- `stderr` , Writable stream or file path to redirect cloud9 stderr.
Example:
@@ -302,17 +302,17 @@ Register two commands, `cloud9 start` and `cloud9 stop`. Unless provided, the Cl
Options:
-- `config` Load the configuration from a config file. Overrides command-line options. Defaults to `null`.
-- `group` Run child processes with a specific group.
-- `user` Run child processes as a specific user.
-- `action` Define an action to execute after the Cloud9 server is started. Defaults to `null`.
-- `ip` IP address where Cloud9 will serve from. Defaults to `"127.0.0.1"`.
-- `port` Port number where Cloud9 will serve from. Defaults to `3000`.
-- `workspace` Path to the workspace that will be loaded in Cloud9, Defaults to `Shell.set('workspace')`.
-- `detach` Wether the Cloud9 process should be attached to the current process. If not defined, default to `true` in shell mode and `false` in command mode.
-- `pidfile` Path to the file storing the detached process id. Defaults to `"/.node_shell/#{md5}.pid"`
-- `stdout` Writable stream or file path to redirect cloud9 stdout.
-- `stderr` Writable stream or file path to redirect cloud9 stderr.
+- `config` , Load the configuration from a config file. Overrides command-line options. Defaults to `null`.
+- `group` , Run child processes with a specific group.
+- `user` , Run child processes as a specific user.
+- `action` , Define an action to execute after the Cloud9 server is started. Defaults to `null`.
+- `ip` , IP address where Cloud9 will serve from. Defaults to `"127.0.0.1"`.
+- `port` , Port number where Cloud9 will serve from. Defaults to `3000`.
+- `workspace`, Path to the workspace that will be loaded in Cloud9, Defaults to `Shell.set('workspace')`.
+- `attach` , Wether the Cloud9 process should be attached to the current process. If not defined, default to `false` (the server run as a daemon).
+- `pidfile` Path to the file storing the daemon process id. Defaults to `"/.node_shell/#{md5}.pid"`
+- `stdout` , Writable stream or file path to redirect cloud9 stdout.
+- `stderr` , Writable stream or file path to redirect cloud9 stderr.
Example:
@@ -350,15 +350,15 @@ Start Coffee in `--watch` mode, so scripts are instantly compiled into Javascrip
Options:
-- `src` Directory where ".coffee" are stored. Each ".coffee" script will be compiled into a .js JavaScript file of the same name.
+- `src` , Directory where ".coffee" are stored. Each ".coffee" script will be compiled into a .js JavaScript file of the same name.
- `output` Directory where compiled JavaScript files are written. Used in conjunction with "compile".
-- `lint` If the `jsl` (JavaScript Lint) command is installed, use it to check the compilation of a CoffeeScript file.
-- `require` Load a library before compiling or executing your script. Can be used to hook in to the compiler (to add Growl notifications, for example).
-- `detach` Wether the Coffee process should be attached to the current process. If not defined, default to `true` in shell mode and `false` in command mode.
-- `pidfile` Path to the file storing the detached process id. Defaults to `"/.node_shell/#{md5}.pid"`
-- `stdout` Writable stream or file path to redirect cloud9 stdout.
-- `stderr` Writable stream or file path to redirect cloud9 stderr.
-- `workspace` Project directory used to resolve relative paths.
+- `lint` , If the `jsl` (JavaScript Lint) command is installed, use it to check the compilation of a CoffeeScript file.
+- `require` , Load a library before compiling or executing your script. Can be used to hook in to the compiler (to add Growl notifications, for example).
+- `attach` , Wether the Coffee process should be attached to the current process. If not defined, default to `false` (the server run as a daemon).
+- `pidfile` , Path to the file storing the daemon process id. Defaults to `"/.node_shell/#{md5}.pid"`
+- `stdout` , Writable stream or file path to redirect cloud9 stdout.
+- `stderr` , Writable stream or file path to redirect cloud9 stderr.
+- `workspace`, Project directory used to resolve relative paths.
Example:
View
1 changes.md
@@ -8,6 +8,7 @@ Version 0.2.4
`Shell.question` has been removed, use `req.question` instead
`Shell.confirm` has been removed, use `req.confirm` instead
Add `styles.unstyle`
+`start_stop` processes are now run as daemon by default
Version 0.2.3
-------------
View
159 lib/start_stop.coffee
@@ -1,58 +1,70 @@
crypto = require 'crypto'
-exec = require('child_process').exec
+child = require 'child_process'
fs = require 'fs'
path = require 'path'
+start_stop = module.exports = {}
+
md5 = (cmd) ->
crypto.createHash('md5').update(cmd).digest('hex')
-getPidfile = (cmd) ->
- dir = process.env['HOME'] + '/.node_shell'
- file = md5 cmd
- createDir = not path.existsSync process.env['HOME'] + '/.node_shell'
+getPidfile = (settings, callback) ->
+ return callback null, settings.pidfile if settings.pidfile
+ dir = path.resolve process.env['HOME'], '.node_shell'
+ file = md5 settings.cmd
+ createDir = not path.existsSync dir
fs.mkdirSync dir, 0700 if createDir
- "#{dir}/#{file}.pid"
+ settings.pidfile = "#{dir}/#{file}.pid"
+ callback null
-module.exports.start = (shell, settings, cmd, callback) ->
- detach = settings.detach ? not shell.isShell
- #if detach and not settings.pidfile
- #throw new Error 'Property settings.pidfile required in detached mode'
- if detach
- cmdStdout = if typeof settings.stdout is 'string' then settings.stdout else '/dev/null'
- cmdStderr = if typeof settings.stderr is 'string' then settings.stderr else '/dev/null'
- pidfile = settings.pidfile or getPidfile cmd
- # return the pid if it match a live process
- pidExists = (pid, callback) ->
- exec "ps -ef | grep #{pid} | grep -v grep", (err, stdout, stderr) ->
- return callback err if err and err.code isnt 1
- return callback null, null if err
- callback null, pid
- #callback err
- # return the pid if it can read it from the pidfile
+###
+* `cmd` Command to run
+* `daemon` Daemonize the child process
+* `pidfile` Path to the file storing the child pid
+* `stdout` Path to the file where stdout is redirected
+* `stderr` Path to the file where stderr is redirected
+###
+start_stop.start = (settings, callback) ->
+ unless settings.attach
+ cmdStdout =
+ if typeof settings.stdout is 'string'
+ then settings.stdout else '/dev/null'
+ cmdStderr =
+ if typeof settings.stderr is 'string'
+ then settings.stderrelse '/dev/null'
+ # Get the pid if it can read it from the pidfile
pidRead = (callback) ->
- path.exists pidfile, (exists) ->
+ path.exists settings.pidfile, (exists) ->
return callback null, null unless exists
- fs.readFile pidfile, (err, pid) ->
+ fs.readFile settings.pidfile, (err, pid) ->
return callback err if err
pid = parseInt pid, 10
callback null, pid
- start = () ->
+ # Start the process
+ start = (callback) ->
pipe = "</dev/null >#{cmdStdout} 2>#{cmdStdout}"
info = 'echo $? $!'
- child = exec "#{cmd} #{pipe} & #{info}", (err, stdout, stderr) ->
+ cmd = "#{settings.cmd} #{pipe} & #{info}"
+ child.exec cmd, (err, stdout, stderr) ->
[code, pid] = stdout.split(' ')
- return callback new Error "Process exit with code #{code}" if code isnt '0'
- fs.writeFileSync pidfile, '' + pid
- callback null, pid
- pidRead (err, pid) ->
- return start() unless pid
- pidExists pid, (err, pid) ->
- return start() unless pid
- callback null, false
+ code = parseInt code, 10
+ pid = parseInt pid, 10
+ if code isnt 0
+ msg = "Process exit with code #{code}"
+ return callback new Error msg
+ fs.writeFile settings.pidfile, '' + pid, (err) ->
+ callback null, pid
+ # Do the all job
+ getPidfile settings, (err) ->
+ pidRead (err, pid) ->
+ return start callback unless pid
+ start_stop.pidRunning pid, (err, pid) ->
+ callback new Error "Pid #{pid} already running" if pid
+ start callback
else # Kill child on exit if started in attached mode
- shell.on 'exit', -> child.kill()
- child = exec cmd
+ #shell.on 'exit', -> child.kill()
+ c = child.exec settings.cmd
if typeof settings.stdout is 'string'
stdout = fs.createWriteStream settings.stdout
else if settings.stdout isnt null and typeof settings.stdout is 'object'
@@ -65,47 +77,46 @@ module.exports.start = (shell, settings, cmd, callback) ->
stderr = settings.stderr
else
stderr = null
- child.stderr.pipe stdout if stdout
- child.stderr.pipe stderr if stderr
+ #process.stdout.pipe stdout if stdout
+ #process.stderr.pipe stderr if stderr
process.nextTick ->
# Block the command if not in shell and process is attached
- return if not shell.isShell and settings.detach is false
- callback null, child.pid
- child
+ #return if not shell.isShell and settings.daemon
+ callback null, c.pid
-
-module.exports.stop = (shell, settings, cmdOrChild, callback) ->
- detach = settings.detach ? not shell.isShell
- if detach
- pidfile = settings.pidfile or getPidfile cmdOrChild
- #return callback null, false unless path.existsSync pidfile
- path.exists pidfile, (exists) ->
- return callback null, false unless exists
- pid = fs.readFileSync pidfile, 'ascii'
- pid = pid.trim()
- cmds = []
- cmds.push "for i in `ps -ef| awk '$3 == '#{pid}' { print $2 }'` ; do kill $i ; done"
- cmds.push "kill #{pid}"
- cmds = cmds.join ' && '
- child = exec cmds, (err, stdout, stderr) ->
- if err and err.code is 1 and /No such process/.test(stderr)
- return fs.unlink pidfile, (err) ->
+start_stop.stop = (settings, callback) ->
+ if typeof settings is 'string' or typeof settings is 'number'
+ settings = {pid: parseInt(settings, 10), attach: true}
+ kill = (pid, callback) ->
+ cmds = """
+ for i in `ps -ef | awk '$3 == '#{pid}' { print $2 }'`
+ do
+ kill $i
+ done
+ kill #{pid}
+ """
+ child.exec cmds, (err, stdout, stderr) ->
+ callback err
+ unless settings.attach
+ getPidfile settings, (err) ->
+ #return callback null, false unless path.existsSync pidfile
+ path.exists settings.pidfile, (exists) ->
+ return callback null, false unless exists
+ pid = fs.readFileSync settings.pidfile, 'ascii'
+ pid = pid.trim()
+ return fs.unlink settings.pidfile, (err) ->
+ return callback err if err
+ kill pid, (err, stdout, stderr) ->
return callback err if err
- return callback null, false
- else if err
- return callback err
- callback null, true
- #child.on 'exit', (code) ->
- #return callback new Error "Unexpected exit code #{code}" unless code is 0
- #fs.unlinkSync pidfile
- #callback null, true
+ callback null, true
else
- pid = cmdOrChild.pid
- cmds = []
- cmds.push "for i in `ps -ef | awk '$3 == '#{pid}' { print $2 }'` ; do kill $i ; done"
- cmds.push "kill #{pid}"
- cmds = cmds.join ' && '
- child = exec(cmds)
- child.on 'exit', (code) ->
- return callback new Error "Unexpected exit code #{code}" unless code is 0
+ kill settings.pid, (err) ->
+ return callback new Error "Unexpected exit code #{err.code}" if err
callback null, true
+###
+Get the pid if it match a live process
+###
+start_stop.pidRunning = (pid, callback) ->
+ child.exec "ps -ef | grep #{pid} | grep -v grep", (err, stdout, stderr) ->
+ return callback err if err and err.code isnt 1
+ callback null, not err
View
2 samples/cloud9/sample.js
@@ -14,7 +14,7 @@
stdout: __dirname+'/cloud9.out.log',
stderr: __dirname+'/cloud9.err.log',
pidfile: __dirname+'/cloud9.pid',
- detach: true
+ attach: false
}));
app.use(shell.router({
shell: app
View
4 samples/coffee/sample.coffee
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env coffee
shell = require 'shell'
@@ -15,7 +15,7 @@
stdout: __dirname + '/coffee.out.log'
stderr: __dirname + '/coffee.err.log'
pidfile: __dirname + '/coffee.pid'
- detach: false
+ detach: true
app.use shell.help
shell: app
introduction: true
View
10 samples/http/sample.js
@@ -1,9 +1,9 @@
#!/usr/bin/env node
- var fs = require('fs'),
- exec = require('child_process').exec,
- spawn = require('child_process').spawn,
- shell = require('shell');
+ var fs = require('fs');
+ var exec = require('child_process').exec;
+ var spawn = require('child_process').spawn;
+ var shell = require('shell');
process.chdir(__dirname);
@@ -18,7 +18,7 @@
stdout: __dirname + '/logs/http.out.log',
stderr: __dirname + '/logs/http.err.log',
pidfile: __dirname + '/tmp/http.pid',
- detach: true
+ attach: false
}));
app.use(shell.router({shell: app}));
app.use(shell.help({shell: app, introduction: true}));
View
2 test/PluginHttpTest.coffee
@@ -6,7 +6,7 @@ http = require 'http'
module.exports =
'Http start/stop': (next) ->
app = shell
- workspace: "#{__dirname}/plugins_http"
+ workspace: "#{__dirname}/PluginsHtpp"
command: null
stdin: new shell.NullStream
stdout: new shell.NullStream
View
0 test/plugins_http/app.coffee → test/PluginsHtpp/app.coffee
File renamed without changes.
View
65 test/StartStopTest.coffee
@@ -0,0 +1,65 @@
+
+fs = require 'fs'
+path = require 'path'
+assert = require 'assert'
+start_stop = require '../lib/start_stop'
+
+module.exports =
+ 'Test daemon # start and stop': (next) ->
+ cmd = "node #{__dirname}/StartStopTest/server.js"
+ # Start the process
+ start_stop.start cmd: cmd, (err, pid) ->
+ assert.ifError err
+ assert.ok typeof pid is 'number'
+ # Check if process started
+ start_stop.pidRunning pid, (err, exists) ->
+ assert.ifError err
+ assert.ok exists
+ # Stop process
+ start_stop.stop cmd:cmd, (err) ->
+ assert.ifError err
+ # Check if process stoped
+ start_stop.pidRunning pid, (err, exists) ->
+ assert.ifError err
+ assert.ok not exists
+ next()
+ 'Test daemon # stop inactive process': (next) ->
+ cmd = "node #{__dirname}/StartStopTest/server.js"
+ # Stop process
+ start_stop.stop cmd:cmd, (err, stoped) ->
+ assert.ifError err
+ assert.ok not stoped
+ next()
+ 'Test daemon # stop inactive process with pidfile': (next) ->
+ cmd = "node #{__dirname}/StartStopTest/server.js"
+ pidfile = "#{__dirname}/StartStopTest/pidfile"
+ fs.writeFile pidfile, "1234567", (err) ->
+ # Check process doesnt exists
+ start_stop.pidRunning 1234567, (err, exists) ->
+ assert.ifError err
+ assert.ok not exists
+ # Stop process
+ start_stop.stop {cmd:cmd, pidfile: pidfile}, (err, stoped) ->
+ assert.ok err instanceof Error
+ # Pidfile shall be removed even if pid is invalid
+ path.exists pidfile, (exists) ->
+ assert.ok not exists
+ next()
+ 'Test attach': (next) ->
+ cmd = "#{__dirname}/StartStopTest/server.js"
+ # Start the process
+ start_stop.start {cmd: cmd, attach: true}, (err, pid) ->
+ assert.ifError err
+ assert.ok typeof pid is 'number'
+ # Check if process started
+ start_stop.pidRunning pid, (err, exists) ->
+ assert.ifError err
+ assert.ok exists
+ # Stop process
+ start_stop.stop pid, (err) ->
+ assert.ifError err
+ # Check if process stoped
+ start_stop.pidRunning pid, (err, exists) ->
+ assert.ifError err
+ assert.ok not exists
+ next()
View
8 test/StartStopTest/server.js
@@ -0,0 +1,8 @@
+#!/usr/bin/env node
+
+var http = require('http');
+http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('Hello World\n');
+}).listen(1337, "127.0.0.1");
+console.log('Server running at http://127.0.0.1:1337/');
View
9 test/StartStopTest/test_attach.coffee
@@ -0,0 +1,9 @@
+#!/usr/bin/env coffee
+
+start_stop = require '../../lib/start_stop'
+
+start_stop.start
+ cmd: "#{__dirname}/server.js"
+ attach: true
+, (err, pid) ->
+ # Keep the process active

0 comments on commit 63995ee

Please sign in to comment.