This commit is contained in:
David Dollar
2011-12-08 16:19:19 -08:00
parent f668b87660
commit c5548a345e
4 changed files with 138 additions and 88 deletions
+67 -74
View File
@@ -23,6 +23,7 @@ class Foreman::Engine
@directory = File.expand_path(File.dirname(procfile))
@options = options
@environment = read_environment_files(options[:env])
@output_mutex = Mutex.new
end
def self.load_env!(env_file)
@@ -32,34 +33,17 @@ class Foreman::Engine
def start
proctitle "ruby: foreman master"
termtitle "#{File.basename(@directory)} - foreman (#{processes.size} processes)"
processes.each do |process|
process.color = next_color
fork process
end
termtitle "#{File.basename(@directory)} - foreman"
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
assign_colors
spawn_processes
watch_for_output
watch_for_termination
end
def execute(name)
error "no such process: #{name}" unless process = procfile[name]
process.color = next_color
fork process
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
trap("INT") { puts "SIGINT received"; terminate_gracefully }
watch_for_termination
end
def processes
procfile.processes
end
def port_for(process, num, base_port=nil)
base_port ||= 5000
offset = procfile.process_names.index(process.name) * 100
@@ -68,60 +52,22 @@ class Foreman::Engine
private ######################################################################
def fork(process)
def spawn_processes
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
1.upto(concurrency[process.name]) do |num|
fork_individual(process, num, port_for(process, num, @options[:port]))
end
end
def fork_individual(process, num, port)
apply_environment!
ENV["PORT"] = port.to_s
ENV["PS"] = "#{process.name}.#{num}"
pid = Process.fork do
run(process)
end
info "started with pid #{pid}", process
running_processes[pid] = process
end
def run(process)
proctitle "ruby: foreman #{process.name}"
trap("SIGINT", "IGNORE")
begin
Dir.chdir directory do
IO.popen(process.command, "w+") do |pipe|
trap("SIGTERM") do
Thread.new do
begin
Process.kill("SIGTERM", pipe.pid)
Timeout.timeout(3) { Process.waitall }
rescue Timeout::Error
Process.kill("SIGKILL", pipe.pid)
end
end
end
until pipe.eof?
info pipe.gets, process
end
end
end
rescue Interrupt, Errno::EIO, Errno::ENOENT
begin
info "process exiting", process
rescue Interrupt
procfile.entries.each do |entry|
reader, writer = IO.pipe
entry.spawn(concurrency[entry.name], writer, @directory, @environment).each do |process|
running_processes[process.pid] = process
readers[process] = reader
end
end
end
def kill_all(signal="SIGTERM")
p [:ff]
running_processes.each do |pid, process|
p [:pid, pid]
Process.kill(signal, pid) rescue Errno::ESRCH
end
end
@@ -135,23 +81,53 @@ private ######################################################################
kill_all "SIGKILL"
end
def watch_for_output
Thread.new do
begin
loop do
rs, ws = IO.select(readers.values, [], [], 1)
(rs || []).each do |r|
ps, message = r.gets.split(",", 2)
color = colors[ps.split(".").first]
info message, ps, color
end
end
rescue Exception => ex
puts ex.message
puts ex.backtrace
end
end
end
def watch_for_termination
pid, status = Process.wait2
process = running_processes.delete(pid)
info "process terminated", process
info "process terminated", process.name
terminate_gracefully
kill_all
rescue Errno::ECHILD
end
def info(message, process=nil)
print process.color if process
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
def info(message, name="system", color=Term::ANSIColor.white)
print color
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
print Term::ANSIColor.reset
print message.chomp
puts
end
def print(message=nil)
@output_mutex.synchronize do
$stdout.print message
end
end
def puts(message=nil)
@output_mutex.synchronize do
$stdout.puts message
end
end
def error(message)
puts "ERROR: #{message}"
exit 1
@@ -165,9 +141,8 @@ private ######################################################################
end
end
def pad_process_name(process)
name = process ? "#{ENV["PS"]}" : "system"
name.ljust(longest_process_name + 3) # add 3 for process number padding
def pad_process_name(name="system")
name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
end
def proctitle(title)
@@ -182,6 +157,24 @@ private ######################################################################
@running_processes ||= {}
end
def readers
@readers ||= {}
end
def colors
@colors ||= {}
end
def assign_colors
procfile.entries.each do |entry|
colors[entry.name] = next_color
end
end
def process_by_reader(reader)
readers.invert[reader]
end
def next_color
@current_color ||= -1
@current_color += 1
+40 -6
View File
@@ -2,13 +2,47 @@ require "foreman"
class Foreman::Process
attr_reader :name
attr_reader :command
attr_accessor :color
attr_reader :entry
attr_reader :num
attr_reader :pid
def initialize(name, command)
@name = name
@command = command
def initialize(entry, num)
@entry = entry
@num = num
end
def run(pipe, basedir, environment)
Dir.chdir(basedir) do
with_environment(environment) do
io = IO.popen("#{entry.command} 2>&1", "w+")
@pid = io.pid
output pipe, "started with pid %d"
Thread.new do
until io.eof?
output pipe, io.gets
end
end
end
end
end
def name
"%s.%s" % [ entry.name, num ]
end
private
def output(pipe, message)
pipe.puts "%s,%s" % [ name, message ]
end
def with_environment(environment)
old_env = ENV.each_pair.inject({}) { |h,(k,v)| h.update(k => v) }
environment.each { |k,v| ENV[k] = v }
ret = yield
ENV.clear
old_env.each { |k,v| ENV[k] = v}
ret
end
end
+9 -8
View File
@@ -1,4 +1,5 @@
require "foreman"
require "foreman/procfile_entry"
# A valid Procfile entry is captured by this regex.
# All other lines are ignored.
@@ -10,18 +11,18 @@ require "foreman"
#
class Foreman::Procfile
attr_reader :processes
attr_reader :entries
def initialize(filename)
@processes = parse_procfile(filename)
end
def process_names
processes.map(&:name)
@entries = parse_procfile(filename)
end
def [](name)
processes.detect { |process| process.name == name }
entries.detect { |entry| entry.name == name }
end
def process_names
entries.map(&:name)
end
private
@@ -29,7 +30,7 @@ private
def parse_procfile(filename)
File.read(filename).split("\n").map do |line|
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
Foreman::Process.new($1, $2)
Foreman::ProcfileEntry.new($1, $2)
end
end.compact
end
+22
View File
@@ -0,0 +1,22 @@
require "foreman"
class Foreman::ProcfileEntry
attr_reader :name
attr_reader :command
attr_accessor :color
def initialize(name, command)
@name = name
@command = command
end
def spawn(num, pipe, basedir, environment)
(1..num).to_a.map do |n|
process = Foreman::Process.new(self, n)
process.run(pipe, basedir, environment)
process
end
end
end