class Foreman::Engine
def self.load_env!(env_file)
def self.load_env!(env_file) @environment = read_environment_files(env_file) apply_environment! end
def assign_colors
def assign_colors procfile.entries.each do |entry| colors[entry.name] = next_color end end
def base_port
def base_port options[:port] || 5000 end
def colors
def colors @colors ||= {} end
def error(message)
def error(message) puts "ERROR: #{message}" exit 1 end
def info(message, name="system", color=Term::ANSIColor.white)
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 initialize(procfile, options={})
def initialize(procfile, options={}) @procfile = Foreman::Procfile.new(procfile) @directory = File.expand_path(File.dirname(procfile)) @options = options @environment = read_environment_files(options[:env]) @output_mutex = Mutex.new end
def kill_all(signal="SIGTERM")
def kill_all(signal="SIGTERM") running_processes.each do |pid, process| Process.kill(signal, pid) rescue Errno::ESRCH end end
def longest_process_name
def longest_process_name @longest_process_name ||= begin longest = procfile.process_names.map { |name| name.length }.sort.last longest = 6 if longest < 6 # system longest end end
def next_color
def next_color @current_color ||= -1 @current_color += 1 @current_color >= COLORS.length ? "" : COLORS[@current_color] end
def pad_process_name(name="system")
def pad_process_name(name="system") name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding end
def port_for(process, num, base_port=nil)
def port_for(process, num, base_port=nil) base_port ||= 5000 offset = procfile.process_names.index(process.name) * 100 base_port.to_i + offset + num - 1 end
def print(message=nil)
def print(message=nil) @output_mutex.synchronize do $stdout.print message end end
def process_by_reader(reader)
def process_by_reader(reader) readers.invert[reader] end
def proctitle(title)
def proctitle(title) $0 = title end
def puts(message=nil)
def puts(message=nil) @output_mutex.synchronize do $stdout.puts message end end
def readers
def readers @readers ||= {} end
def running_processes
def running_processes @running_processes ||= {} end
def spawn_processes
def spawn_processes concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency]) procfile.entries.each do |entry| reader, writer = IO.pipe entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process| running_processes[process.pid] = process readers[process] = reader end end end
def start
def start proctitle "ruby: foreman master" 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 terminate_gracefully
def terminate_gracefully info "sending SIGTERM to all processes" kill_all "SIGTERM" Timeout.timeout(5) { Process.waitall } rescue Timeout::Error info "sending SIGKILL to all processes" kill_all "SIGKILL" end
def termtitle(title)
def termtitle(title) printf("\033]0;#{title}\007") end
def watch_for_output
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
def watch_for_termination pid, status = Process.wait2 process = running_processes.delete(pid) info "process terminated", process.name terminate_gracefully kill_all rescue Errno::ECHILD end