diff --git a/lib/foreman/cli.rb b/lib/foreman/cli.rb index cf72b58..f7dc59b 100644 --- a/lib/foreman/cli.rb +++ b/lib/foreman/cli.rb @@ -30,7 +30,7 @@ class Foreman::CLI < Thor def start(process=nil) check_procfile! engine.options[:concurrency] = "#{process}=1" if process - engine.run + engine.start end desc "export FORMAT LOCATION", "Export the application to another process management format" diff --git a/lib/foreman/engine.rb b/lib/foreman/engine.rb index 0ca29ac..71953fb 100644 --- a/lib/foreman/engine.rb +++ b/lib/foreman/engine.rb @@ -28,7 +28,7 @@ class Foreman::Engine @output_mutex = Mutex.new end - def run + def start proctitle "ruby: foreman master" termtitle "#{File.basename(@directory)} - foreman" @@ -36,49 +36,9 @@ class Foreman::Engine trap("INT") { puts "SIGINT received"; terminate_gracefully } assign_colors - start + spawn_processes watch_for_output watch_for_termination - terminate_gracefully - end - - def start(name=nil) - concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency]) - - procfile.entries.each do |entry| - unless name == nil - next unless entry.name == name - end - - reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY")) - entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process| - running_processes[process.pid] = process - readers[process] = reader - end - end - end - - def stop(name=nil, signal='SIGTERM') - running_processes.each do |pid, process| - unless name == ALL_PROCESSES - # Comparing against process.entry.name instead of process.name to - # make sure we match the process name exactly for any/all - # concurrently running processes by this name - next unless process.entry.name == name - else - info "sending #{signal} to all processes" - end - - process.kill signal - process = running_processes.delete(pid) - Timeout.timeout(5) do - begin - Process.waitpid(pid) - info "process terminated", process.name - rescue Errno::ECHILD - end - end - end end def port_for(process, num, base_port=nil) @@ -93,19 +53,44 @@ class Foreman::Engine private ###################################################################### + def spawn_processes + concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency]) + + procfile.entries.each do |entry| + reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY")) + entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process| + running_processes[process.pid] = process + readers[process] = reader + end + end + end + def base_port options[:port] || 5000 end + def kill_all(signal="SIGTERM") + running_processes.each do |pid, process| + info "sending #{signal} to pid #{pid}" + process.kill signal + end + end + def terminate_gracefully return if @terminating @terminating = true + info "sending SIGTERM to all processes" + kill_all "SIGTERM" Timeout.timeout(5) do - stop + while running_processes.length > 0 + pid, status = Process.wait2 + process = running_processes.delete(pid) + info "process terminated", process.name + end end rescue Timeout::Error - stop(nil, 'SIGKILL') - rescue Errno::ECHILD + info "sending SIGKILL to all processes" + kill_all "SIGKILL" end def poll_readers @@ -138,6 +123,7 @@ private ###################################################################### pid, status = Process.wait2 process = running_processes.delete(pid) info "process terminated", process.name + terminate_gracefully rescue Errno::ECHILD end diff --git a/spec/foreman/cli_spec.rb b/spec/foreman/cli_spec.rb index 7a4d9c1..405b8a2 100644 --- a/spec/foreman/cli_spec.rb +++ b/spec/foreman/cli_spec.rb @@ -21,7 +21,7 @@ describe "Foreman::CLI", :fakefs do it "runs successfully" do dont_allow(subject).error - mock.instance_of(Foreman::Engine).run + mock.instance_of(Foreman::Engine).start subject.start end diff --git a/spec/foreman/engine_spec.rb b/spec/foreman/engine_spec.rb index 07bfe53..a7e8516 100644 --- a/spec/foreman/engine_spec.rb +++ b/spec/foreman/engine_spec.rb @@ -28,15 +28,14 @@ describe "Foreman::Engine", :fakefs do end end - describe "run" do + describe "start" do it "forks the processes" do write_procfile mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./alpha", is_a(IO)) mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO)) mock(subject).watch_for_output mock(subject).watch_for_termination - mock(subject).terminate_gracefully - subject.run + subject.start end it "handles concurrency" do @@ -46,8 +45,7 @@ describe "Foreman::Engine", :fakefs do mock.instance_of(Foreman::Process).run_process(Dir.pwd, "./bravo", is_a(IO)).never mock(engine).watch_for_output mock(engine).watch_for_termination - mock(engine).terminate_gracefully - engine.run + engine.start end end @@ -57,7 +55,7 @@ describe "Foreman::Engine", :fakefs do stub(Process).fork any_instance_of(Foreman::Engine) do |engine| stub(engine).info - stub(engine).start + stub(engine).spawn_processes stub(engine).watch_for_termination end end @@ -66,7 +64,7 @@ describe "Foreman::Engine", :fakefs do File.open("/tmp/env", "w") { |f| f.puts("FOO=baz") } engine = Foreman::Engine.new("Procfile", :env => "/tmp/env") engine.environment.should == {"FOO"=>"baz"} - engine.run + engine.start end it "should read more than one if specified" do @@ -74,7 +72,7 @@ describe "Foreman::Engine", :fakefs do File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") } engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2") engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" } - engine.run + engine.start end it "should handle quoted values" do @@ -97,7 +95,7 @@ describe "Foreman::Engine", :fakefs do File.open(".env", "w") { |f| f.puts("FOO=qoo") } engine = Foreman::Engine.new("Procfile") engine.environment.should == {"FOO"=>"qoo"} - engine.run + engine.start end end @@ -111,8 +109,7 @@ describe "Foreman::Engine", :fakefs do it "should spawn" do stub(subject).watch_for_output stub(subject).watch_for_termination - stub(subject).terminate_gracefully - subject.run + subject.start Process.waitall mock(subject).info(/started with pid \d+/, "utf8.1", anything) mock(subject).info("\xff\x03\n", "utf8.1", anything)