Skip to content

Instantly share code, notes, and snippets.

@artemave
Last active October 1, 2015 05:08
Show Gist options
  • Save artemave/1927233 to your computer and use it in GitHub Desktop.
Save artemave/1927233 to your computer and use it in GitHub Desktop.
ChildFork

ChildFork

Like ChildProcess but forks instead of creating new process. In certain cases (e.g, child code shares heavy initialization with parent; like ActiveRecord, etc.) should perform better than ChildProcess. On the flip side, it does not work on Windows and Jruby.

The main difference from plain Process.fork is that this one is guaranteed to not outlive parent process. Plus it implements some of ChildProcess api, so that those two can be hidden behind common interface.

Usage:

# Gemfile
gem 'child_fork', git: 'git://gist.github.com/1927233.git'

# code
require 'child_fork'

child = ChildFork.new do
  LongAndPainfulJob.new
end

child.alive? # true

child.stop # force stop

Running the tests

Install the rspec gem

gem install rspec

Run the spec

rspec child_fork_spec.rb
Gem::Specification.new do |s|
s.name = 'child_fork'
s.summary = 'Like ChildProcess but forks instead of creating new process'
s.version = '0.0.2'
s.platform = Gem::Platform::RUBY
s.files = ['lib/child_fork.rb']
s.require_path = 'lib'
s.author = 'Artem Avetisyan'
s.email = 'artemave@gmail.com'
s.homepage = 'https://gist.github.com/1927233'
s.test_files = ['spec/child_fork_spec.rb']
s.add_development_dependency('rspec', ["~> 2.8"])
end
class ChildFork
attr_reader :pid
def initialize
@pid = Kernel.fork do
trap('USR1') do
Process.kill('TERM', Process.pid)
end
at_exit { exit! }
begin
yield
rescue => e
puts "#{self} has raised #{e.inspect}:"
puts e.backtrace.join("\n")
end
end
Process.detach(@pid)
at_exit { stop }
end
def alive?
Process.kill(0, @pid)
true
rescue Errno::ESRCH
false
end
def stop
Process.kill('USR1', @pid) rescue Errno::ESRCH # no such process
end
end
require 'tempfile'
require File.expand_path('../../lib/child_fork', __FILE__)
describe ChildFork do
it 'forks passed block' do
ppid_file = Tempfile.new('ppidfile')
pid_file = Tempfile.new('pidfile')
fork do
pid_file.write(Process.pid)
pid_file.rewind
at_exit { exit! }
ChildFork.new do
ppid_file.write(Process.ppid)
ppid_file.rewind
sleep 2
end
sleep 0.5
end
Process.wait
ppid_file.read.should == pid_file.read
end
it 'ensures no zombies' do
Kernel.stub(:fork).and_return(pid = 1)
Process.should_receive(:detach).with(pid)
ChildFork.new {1}
end
it 'knows when it is running' do
res_file = Tempfile.new('res')
fork do
at_exit { exit! }
child = ChildFork.new { sleep 0.5 }
res_file.write(child.alive?)
res_file.rewind
end
Process.wait
res_file.read.should == 'true'
end
context 'shuts down child process' do
let(:child_pid) do
Tempfile.new('child_pid')
end
let(:child_alive?) do
begin
Process.kill(0, child_pid.read.to_i)
true
rescue Errno::ESRCH
false
end
end
it 'when stopped' do
child_pid
fork do
at_exit { exit! }
child = ChildFork.new { sleep 2 }
child_pid.write(child.pid)
child_pid.rewind
child.stop
end
Process.wait
sleep 0.5
child_alive?.should == false
end
it 'when exits normally' do
child_pid # Magic touch. Literally. Else Tempfile gets created in fork and that messes things up
fork do
at_exit { exit! }
child = ChildFork.new { sleep 2 }
child_pid.write(child.pid)
child_pid.rewind
end
Process.wait
sleep 0.5
child_alive?.should == false
end
it 'when killed violently' do
child_pid
fork do
at_exit { exit! }
child = ChildFork.new { sleep 2 }
child_pid.write(child.pid)
child_pid.rewind
Process.kill('TERM', Process.pid)
end
sleep 0.5
child_alive?.should == false
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment