weaver
Version:
Interactive process management system
354 lines (262 loc) • 6.59 kB
text/coffeescript
assert = require('assert')
resolve = require('path').resolve
fork = require('child_process').spawn
EventEmitter = require('events').EventEmitter
Watcher = require('./watcher')
###*
#* Status codes
#* R - restart
#* E - error
#* D - done (clean exit)
#* W - work in progress
#* S - stopped
###
# Which subtask parameters can be changed without restart
mutable =
count : yes
source : no
cwd : no
env : no
persistent : yes
executable : no
timeout : yes
runtime : yes
watch : yes
arguments : no
class Task extends EventEmitter
: Object.create(null)
: (name) ->
return [name] ?= new Task(name)
: (name) ->
if name of
Watcher.stop( [name].watchHandler)
delete [name]
return
: ->
now = Date.now()
status = {}
for name, task of
status[name] =
count : task.count
source : task.source
restart : task.restart
subtasks : task.subtasks.map (subtask) ->
pid : subtask.pid
args : subtask.args
status : subtask.status
uptime : now - subtask.start
return status
log: ->
active: yes
constructor: ( ) ->
= []
= (error) =>
if error
else
return
return @
# Upgrade task
upgrade: (options = {}) ->
restartRequired = no
for own key of mutable
try
assert.deepEqual(@[key], options[key])
catch change
# Force restart when one of non-mutable keys gets modified
restartRequired = restartRequired or not mutable[key]
# Restart existing
if restartRequired and .length
# Spawn required
for index in [0...( or 0)]
subtask = [index]
if not subtask or (subtask.status is 'R' and not subtask.pid)
# Kill redundant
while .length > ( or 0)
return
# Upgrade task parameter with given value
upgradeParameter: (key, value) ->
if value?
@[key] = value
else
delete @[key]
switch key
when 'watch'
Watcher.stop( )
Watcher.start( , or [], )
return
# Spawn subtask
spawn: (id) ->
args = or []
binary = process.execPath
subtask =
id : id
status : 'W'
name :
start : Date.now()
env :
subtask.args = for argument in args
if Array.isArray(argument) then argument[id] else argument
eargs = subtask.args.slice()
if
binary =
else
eargs.unshift( )
subtask.process = fork(binary, eargs, {
stdio : 'pipe'
cwd : resolve( )
env : subtask.env
})
subtask.pid = subtask.process.pid or 0
if subtask.pid
# Setup logger
subtask.process.stdout.on('data', .bind(@, subtask.pid))
subtask.process.stderr.on('data', .bind(@, subtask.pid))
# Setup exit handler
subtask.process.once('exit', .bind(@, 'exit', subtask))
subtask.process.once('error', (error) =>
subtask.status = 'E'
subtask.code = 255
subtask.pid = 0
)
[id] = subtask
return
# Call fn for each subtask
foreach: (fn, argument) ->
for subtask in
fn.call(@, subtask, argument)
return
# Kill subtask with signal
killSubtask: (subtask, signal) ->
if subtask and subtask.pid
try
subtask.process.kill(signal)
catch error
return
# Stop subtask
stopSubtask: (subtask) ->
if subtask and subtask.pid
subtask.process.kill('SIGINT')
setTimeout((->
if subtask.pid
subtask.process.kill('SIGTERM')
), or 1000)
# Restart subtask
restartSubtask: (subtask) ->
if subtask
subtask.status = 'R'
return
# Get subtask by pid
getPID: (pid) ->
for subtask in when subtask and subtask.pid is pid
return subtask
return
# Kill subtask by pid with signal
killPID: (pid, signal) ->
if pid?
else
return
# Restart subtask by pid
restartPID: (pid) ->
if pid?
else
return
# Stop subtask by pid
stopPID: (pid) ->
if pid?
else
return
# Kill all subtasks
killSubtasks: ->
return
# Restart all subtasks
restartSubtasks: ->
return
# Stop all subtasks
stopSubtasks: ->
return
# Drop task
dropSubtasks: ->
if
= no
unless .length
Task.destroy( )
else
return
activeSubtasks: ->
return .filter((subtask) -> subtask.pid)
exitHandler: (subtask, code, signal) ->
restartRequired =
if code is null
else
subtask.pid = 0
subtask.code = code
subtask.signal = signal
delete subtask.process
if subtask.status isnt 'R'
if code
subtask.status = 'E'
else if signal
subtask.status = 'S'
else
subtask.status = 'D'
if restartRequired and code
elapsed = Date.now() - subtask.start
if elapsed < ( or 1000)
restartRequired = no
# Restart requested
if subtask.status is 'R'
restartRequired = yes
# Task dropped
unless
restartRequired = no
unless .length
Task.destroy( )
if restartRequired
return
logHandler: (pid, data) ->
return
expandEnv: ->
expanded = {}
expanded.HOME = process.env.HOME
expanded.PATH = process.env.PATH
unless
expanded.NODE_PATH = process.env.NODE_PATH
for own key, value of
switch value
when true
if process.env.hasOwnProperty(key)
expanded[key] = process.env[key]
when false
delete expanded[key]
else
expanded[key] = [key]
return expanded
module.exports = Task