2015-12-16 00:07:31 -05:00
|
|
|
# frozen_string_literal: false
|
2003-09-04 12:18:59 -04:00
|
|
|
require 'test/unit'
|
2005-06-03 10:39:15 -04:00
|
|
|
require 'timeout'
|
2011-05-18 09:36:46 -04:00
|
|
|
require 'tempfile'
|
2003-09-04 12:18:59 -04:00
|
|
|
|
|
|
|
class TestSignal < Test::Unit::TestCase
|
|
|
|
def test_signal
|
2003-10-19 22:31:47 -04:00
|
|
|
begin
|
2006-12-31 10:02:22 -05:00
|
|
|
x = 0
|
|
|
|
oldtrap = Signal.trap(:INT) {|sig| x = 2 }
|
|
|
|
Process.kill :INT, Process.pid
|
2009-10-18 07:19:33 -04:00
|
|
|
10.times do
|
|
|
|
break if 2 == x
|
|
|
|
sleep 0.1
|
|
|
|
end
|
2006-12-31 10:02:22 -05:00
|
|
|
assert_equal 2, x
|
2003-09-05 11:15:43 -04:00
|
|
|
|
2006-12-31 10:02:22 -05:00
|
|
|
Signal.trap(:INT) { raise "Interrupt" }
|
2013-10-09 04:43:12 -04:00
|
|
|
assert_raise_with_message(RuntimeError, /Interrupt/) {
|
2006-12-31 10:02:22 -05:00
|
|
|
Process.kill :INT, Process.pid
|
2003-09-04 12:18:59 -04:00
|
|
|
sleep 0.1
|
2006-12-31 10:02:22 -05:00
|
|
|
}
|
2003-10-19 22:31:47 -04:00
|
|
|
ensure
|
2006-12-31 10:02:22 -05:00
|
|
|
Signal.trap :INT, oldtrap if oldtrap
|
2003-09-04 12:18:59 -04:00
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2005-06-03 10:39:15 -04:00
|
|
|
|
2011-05-15 09:37:47 -04:00
|
|
|
def test_signal_process_group
|
|
|
|
bug4362 = '[ruby-dev:43169]'
|
|
|
|
assert_nothing_raised(bug4362) do
|
2018-07-02 20:29:06 -04:00
|
|
|
cmd = [ EnvUtil.rubybin, '--disable=gems' '-e', 'sleep 10' ]
|
|
|
|
pid = Process.spawn(*cmd, :pgroup => true)
|
2011-05-15 09:37:47 -04:00
|
|
|
Process.kill(:"-TERM", pid)
|
|
|
|
Process.waitpid(pid)
|
|
|
|
assert_equal(true, $?.signaled?)
|
|
|
|
assert_equal(Signal.list["TERM"], $?.termsig)
|
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill) and
|
|
|
|
Process.respond_to?(:pgroup) # for mswin32
|
2011-05-15 09:37:47 -04:00
|
|
|
|
2005-06-03 10:39:15 -04:00
|
|
|
def test_exit_action
|
2013-06-19 11:57:14 -04:00
|
|
|
if Signal.list[sig = "USR1"]
|
|
|
|
term = :TERM
|
|
|
|
else
|
|
|
|
sig = "INT"
|
|
|
|
term = :KILL
|
|
|
|
end
|
2018-07-02 20:29:06 -04:00
|
|
|
IO.popen([EnvUtil.rubybin, '--disable=gems', '-e', <<-"End"], 'r+') do |io|
|
2013-06-19 11:57:14 -04:00
|
|
|
Signal.trap(:#{sig}, "EXIT")
|
|
|
|
STDOUT.syswrite("a")
|
2008-10-23 21:28:32 -04:00
|
|
|
Thread.start { sleep(2) }
|
2013-06-19 11:57:14 -04:00
|
|
|
STDIN.sysread(4096)
|
2010-08-09 09:12:54 -04:00
|
|
|
End
|
2013-06-19 11:57:14 -04:00
|
|
|
pid = io.pid
|
|
|
|
io.sysread(1)
|
2005-06-03 10:39:15 -04:00
|
|
|
sleep 0.1
|
|
|
|
assert_nothing_raised("[ruby-dev:26128]") {
|
2013-06-19 11:57:14 -04:00
|
|
|
Process.kill(term, pid)
|
2005-06-05 03:04:06 -04:00
|
|
|
begin
|
2006-12-31 10:02:22 -05:00
|
|
|
Timeout.timeout(3) {
|
2005-06-05 03:04:06 -04:00
|
|
|
Process.waitpid pid
|
|
|
|
}
|
|
|
|
rescue Timeout::Error
|
2013-06-19 03:47:12 -04:00
|
|
|
if term
|
|
|
|
Process.kill(term, pid)
|
|
|
|
term = (:KILL if term != :KILL)
|
|
|
|
retry
|
|
|
|
end
|
2005-06-05 03:04:06 -04:00
|
|
|
raise
|
|
|
|
end
|
2005-06-03 10:39:15 -04:00
|
|
|
}
|
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
def test_invalid_signal_name
|
|
|
|
assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) }
|
2014-07-30 10:27:15 -04:00
|
|
|
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.kill("\u{30eb 30d3 30fc}", $$) }
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
def test_signal_exception
|
|
|
|
assert_raise(ArgumentError) { SignalException.new }
|
|
|
|
assert_raise(ArgumentError) { SignalException.new(-1) }
|
|
|
|
assert_raise(ArgumentError) { SignalException.new(:XXXXXXXXXX) }
|
2014-03-30 22:34:43 -04:00
|
|
|
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { SignalException.new("\u{30eb 30d3 30fc}") }
|
2008-05-30 09:42:23 -04:00
|
|
|
Signal.list.each do |signm, signo|
|
|
|
|
next if signm == "EXIT"
|
2018-03-06 08:43:54 -05:00
|
|
|
assert_equal(signo, SignalException.new(signm).signo, signm)
|
|
|
|
assert_equal(signo, SignalException.new(signm.to_sym).signo, signm)
|
|
|
|
assert_equal(signo, SignalException.new(signo).signo, signo)
|
2008-05-30 09:42:23 -04:00
|
|
|
end
|
2018-03-10 06:26:54 -05:00
|
|
|
e = assert_raise(ArgumentError) {SignalException.new("-SIGEXIT")}
|
|
|
|
assert_not_match(/SIG-SIG/, e.message)
|
2008-05-30 09:42:23 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_interrupt
|
|
|
|
assert_raise(Interrupt) { raise Interrupt.new }
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_signal2
|
|
|
|
begin
|
|
|
|
x = false
|
|
|
|
oldtrap = Signal.trap(:INT) {|sig| x = true }
|
|
|
|
GC.start
|
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Process.kill }
|
|
|
|
|
|
|
|
Timeout.timeout(10) do
|
|
|
|
x = false
|
|
|
|
Process.kill(SignalException.new(:INT).signo, $$)
|
2011-05-27 09:37:37 -04:00
|
|
|
sleep(0.01) until x
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
x = false
|
|
|
|
Process.kill("INT", $$)
|
2011-05-27 09:37:37 -04:00
|
|
|
sleep(0.01) until x
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
x = false
|
|
|
|
Process.kill("SIGINT", $$)
|
2011-05-27 09:37:37 -04:00
|
|
|
sleep(0.01) until x
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
x = false
|
|
|
|
o = Object.new
|
|
|
|
def o.to_str; "SIGINT"; end
|
|
|
|
Process.kill(o, $$)
|
2011-05-27 09:37:37 -04:00
|
|
|
sleep(0.01) until x
|
2008-05-30 09:42:23 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Process.kill(Object.new, $$) }
|
|
|
|
|
|
|
|
ensure
|
|
|
|
Signal.trap(:INT, oldtrap) if oldtrap
|
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
def test_trap
|
|
|
|
begin
|
|
|
|
oldtrap = Signal.trap(:INT) {|sig| }
|
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Signal.trap }
|
|
|
|
|
|
|
|
# FIXME!
|
|
|
|
Signal.trap(:INT, nil)
|
|
|
|
Signal.trap(:INT, "")
|
|
|
|
Signal.trap(:INT, "SIG_IGN")
|
|
|
|
Signal.trap(:INT, "IGNORE")
|
|
|
|
|
|
|
|
Signal.trap(:INT, "SIG_DFL")
|
|
|
|
Signal.trap(:INT, "SYSTEM_DEFAULT")
|
|
|
|
|
|
|
|
Signal.trap(:INT, "EXIT")
|
|
|
|
|
2008-07-16 15:12:41 -04:00
|
|
|
Signal.trap(:INT, "xxxxxx")
|
|
|
|
Signal.trap(:INT, "xxxx")
|
2008-05-30 09:42:23 -04:00
|
|
|
|
|
|
|
Signal.trap(SignalException.new(:INT).signo, "SIG_DFL")
|
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Signal.trap(-1, "xxxx") }
|
|
|
|
|
|
|
|
o = Object.new
|
|
|
|
def o.to_str; "SIGINT"; end
|
|
|
|
Signal.trap(o, "SIG_DFL")
|
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") }
|
|
|
|
|
2014-07-30 10:27:15 -04:00
|
|
|
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Signal.trap("\u{30eb 30d3 30fc}", "SIG_DFL") }
|
2018-03-10 05:36:35 -05:00
|
|
|
|
|
|
|
assert_raise(ArgumentError) { Signal.trap("EXIT\0") {} }
|
2008-05-30 09:42:23 -04:00
|
|
|
ensure
|
|
|
|
Signal.trap(:INT, oldtrap) if oldtrap
|
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2010-04-27 08:27:13 -04:00
|
|
|
|
2014-09-20 22:03:34 -04:00
|
|
|
%w"KILL STOP".each do |sig|
|
|
|
|
if Signal.list.key?(sig)
|
|
|
|
define_method("test_trap_uncatchable_#{sig}") do
|
|
|
|
assert_raise(Errno::EINVAL, "SIG#{sig} is not allowed to be caught") { Signal.trap(sig) {} }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-21 01:10:14 -04:00
|
|
|
def test_sigexit
|
|
|
|
assert_in_out_err([], 'Signal.trap(:EXIT) {print "OK"}', ["OK"])
|
|
|
|
assert_in_out_err([], 'Signal.trap("EXIT") {print "OK"}', ["OK"])
|
|
|
|
assert_in_out_err([], 'Signal.trap(:SIGEXIT) {print "OK"}', ["OK"])
|
|
|
|
assert_in_out_err([], 'Signal.trap("SIGEXIT") {print "OK"}', ["OK"])
|
|
|
|
assert_in_out_err([], 'Signal.trap(0) {print "OK"}', ["OK"])
|
|
|
|
end
|
|
|
|
|
2010-04-27 08:27:13 -04:00
|
|
|
def test_kill_immediately_before_termination
|
2013-06-19 11:57:11 -04:00
|
|
|
Signal.list[sig = "USR1"] or sig = "INT"
|
|
|
|
assert_in_out_err(["-e", <<-"end;"], "", %w"foo")
|
|
|
|
Signal.trap(:#{sig}) { STDOUT.syswrite("foo") }
|
|
|
|
Process.kill :#{sig}, $$
|
2013-06-19 03:47:15 -04:00
|
|
|
end;
|
|
|
|
end if Process.respond_to?(:kill)
|
2011-05-18 09:36:46 -04:00
|
|
|
|
2014-05-09 19:48:47 -04:00
|
|
|
def test_trap_system_default
|
|
|
|
assert_separately([], <<-End)
|
|
|
|
trap(:QUIT, "SYSTEM_DEFAULT")
|
|
|
|
assert_equal("SYSTEM_DEFAULT", trap(:QUIT, "DEFAULT"))
|
|
|
|
End
|
2014-05-09 21:00:37 -04:00
|
|
|
end if Signal.list.key?('QUIT')
|
2014-05-09 19:48:47 -04:00
|
|
|
|
2011-07-12 00:55:50 -04:00
|
|
|
def test_reserved_signal
|
|
|
|
assert_raise(ArgumentError) {
|
|
|
|
Signal.trap(:SEGV) {}
|
|
|
|
}
|
|
|
|
assert_raise(ArgumentError) {
|
|
|
|
Signal.trap(:BUS) {}
|
|
|
|
}
|
|
|
|
assert_raise(ArgumentError) {
|
|
|
|
Signal.trap(:ILL) {}
|
|
|
|
}
|
|
|
|
assert_raise(ArgumentError) {
|
|
|
|
Signal.trap(:FPE) {}
|
|
|
|
}
|
|
|
|
assert_raise(ArgumentError) {
|
|
|
|
Signal.trap(:VTALRM) {}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2012-11-19 04:43:53 -05:00
|
|
|
def test_signame
|
2015-03-01 23:04:30 -05:00
|
|
|
Signal.list.each do |name, num|
|
|
|
|
assert_equal(num, Signal.list[Signal.signame(num)], name)
|
|
|
|
end
|
|
|
|
assert_nil(Signal.signame(-1))
|
|
|
|
signums = Signal.list.invert
|
|
|
|
assert_nil(Signal.signame((1..1000).find {|num| !signums[num]}))
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_signame_delivered
|
2018-07-02 20:29:06 -04:00
|
|
|
args = [EnvUtil.rubybin, "--disable=gems", "-e", <<"", :err => File::NULL]
|
|
|
|
Signal.trap("INT") do |signo|
|
|
|
|
signame = Signal.signame(signo)
|
|
|
|
Marshal.dump(signame, STDOUT)
|
|
|
|
STDOUT.flush
|
|
|
|
exit 0
|
|
|
|
end
|
|
|
|
Process.kill("INT", $$)
|
|
|
|
sleep 1 # wait signal deliver
|
2012-11-20 00:39:14 -05:00
|
|
|
|
2018-07-02 20:29:06 -04:00
|
|
|
10.times do
|
|
|
|
IO.popen(args) do |child|
|
2012-11-19 19:50:58 -05:00
|
|
|
signame = Marshal.load(child)
|
2018-03-06 08:43:54 -05:00
|
|
|
assert_equal("INT", signame)
|
2012-11-19 04:43:53 -05:00
|
|
|
end
|
|
|
|
end
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2012-11-28 03:30:51 -05:00
|
|
|
|
|
|
|
def test_trap_puts
|
|
|
|
assert_in_out_err([], <<-INPUT, ["a"*10000], [])
|
|
|
|
Signal.trap(:INT) {
|
|
|
|
# for enable internal io mutex
|
2013-01-31 02:08:23 -05:00
|
|
|
STDOUT.sync = false
|
2012-11-28 03:30:51 -05:00
|
|
|
# larger than internal io buffer
|
|
|
|
print "a"*10000
|
|
|
|
}
|
|
|
|
Process.kill :INT, $$
|
|
|
|
sleep 0.1
|
|
|
|
INPUT
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2013-03-19 00:40:22 -04:00
|
|
|
|
|
|
|
def test_hup_me
|
|
|
|
# [Bug #7951] [ruby-core:52864]
|
2013-03-19 14:34:13 -04:00
|
|
|
# This is MRI specific spec. ruby has no guarantee
|
|
|
|
# that signal will be deliverd synchronously.
|
|
|
|
# This ugly workaround was introduced to don't break
|
|
|
|
# compatibility against silly example codes.
|
2014-05-09 12:15:30 -04:00
|
|
|
assert_separately([], <<-RUBY)
|
2014-05-09 11:13:11 -04:00
|
|
|
trap(:HUP, "DEFAULT")
|
2013-03-19 00:40:22 -04:00
|
|
|
assert_raise(SignalException) {
|
2013-03-21 10:17:10 -04:00
|
|
|
Process.kill('HUP', Process.pid)
|
|
|
|
}
|
2014-05-09 11:13:11 -04:00
|
|
|
RUBY
|
2013-03-21 10:17:10 -04:00
|
|
|
bug8137 = '[ruby-dev:47182] [Bug #8137]'
|
|
|
|
assert_nothing_raised(bug8137) {
|
|
|
|
Timeout.timeout(1) {
|
|
|
|
Process.kill(0, Process.pid)
|
|
|
|
}
|
2013-03-19 00:40:22 -04:00
|
|
|
}
|
2013-06-19 03:47:15 -04:00
|
|
|
end if Process.respond_to?(:kill) and Signal.list.key?('HUP')
|
2014-05-10 12:10:32 -04:00
|
|
|
|
|
|
|
def test_ignored_interrupt
|
|
|
|
bug9820 = '[ruby-dev:48203] [Bug #9820]'
|
|
|
|
assert_separately(['-', bug9820], <<-'end;') # begin
|
|
|
|
bug = ARGV.shift
|
|
|
|
trap(:INT, "IGNORE")
|
|
|
|
assert_nothing_raised(SignalException, bug) do
|
|
|
|
Process.kill(:INT, $$)
|
|
|
|
end
|
|
|
|
end;
|
2014-10-06 03:23:06 -04:00
|
|
|
|
|
|
|
if trap = Signal.list['TRAP']
|
|
|
|
bug9820 = '[ruby-dev:48592] [Bug #9820]'
|
2022-01-19 03:43:07 -05:00
|
|
|
no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
|
|
|
|
status = assert_in_out_err(['-e', "#{no_core}Process.kill(:TRAP, $$)"])
|
2014-10-06 03:23:06 -04:00
|
|
|
assert_predicate(status, :signaled?, bug9820)
|
|
|
|
assert_equal(trap, status.termsig, bug9820)
|
|
|
|
end
|
2014-10-07 10:40:16 -04:00
|
|
|
|
|
|
|
if Signal.list['CONT']
|
|
|
|
bug9820 = '[ruby-dev:48606] [Bug #9820]'
|
|
|
|
assert_ruby_status(['-e', 'Process.kill(:CONT, $$)'])
|
|
|
|
end
|
2014-05-10 12:10:32 -04:00
|
|
|
end if Process.respond_to?(:kill)
|
2016-01-26 00:33:28 -05:00
|
|
|
|
|
|
|
def test_signal_list_dedupe_keys
|
|
|
|
a = Signal.list.keys.map(&:object_id).sort
|
|
|
|
b = Signal.list.keys.map(&:object_id).sort
|
|
|
|
assert_equal a, b
|
|
|
|
end
|
2017-06-12 00:52:25 -04:00
|
|
|
|
|
|
|
def test_self_stop
|
|
|
|
assert_ruby_status([], <<-'end;')
|
|
|
|
begin
|
|
|
|
fork{
|
|
|
|
sleep 1
|
|
|
|
Process.kill(:CONT, Process.ppid)
|
|
|
|
}
|
|
|
|
Process.kill(:STOP, Process.pid)
|
|
|
|
rescue NotImplementedError
|
|
|
|
# ok
|
|
|
|
end
|
|
|
|
end;
|
|
|
|
end
|
2018-07-07 20:02:27 -04:00
|
|
|
|
|
|
|
def test_sigchld_ignore
|
2022-01-04 03:25:30 -05:00
|
|
|
omit 'no SIGCHLD' unless Signal.list['CHLD']
|
2018-07-07 20:02:27 -04:00
|
|
|
old = trap(:CHLD, 'IGNORE')
|
|
|
|
cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ]
|
|
|
|
assert(system(*cmd, 'exit!(0)'), 'no ECHILD')
|
|
|
|
IO.pipe do |r, w|
|
|
|
|
pid = spawn(*cmd, "STDIN.read", in: r)
|
|
|
|
nb = Process.wait(pid, Process::WNOHANG)
|
|
|
|
th = Thread.new(Thread.current) do |parent|
|
|
|
|
Thread.pass until parent.stop? # wait for parent to Process.wait
|
|
|
|
w.close
|
|
|
|
end
|
|
|
|
assert_raise(Errno::ECHILD) { Process.wait(pid) }
|
2019-06-29 20:33:24 -04:00
|
|
|
th.join
|
2018-07-07 20:02:27 -04:00
|
|
|
assert_nil nb
|
|
|
|
end
|
|
|
|
|
|
|
|
IO.pipe do |r, w|
|
|
|
|
pids = 3.times.map { spawn(*cmd, 'exit!', out: w) }
|
|
|
|
w.close
|
|
|
|
zombies = pids.dup
|
|
|
|
assert_nil r.read(1), 'children dead'
|
|
|
|
|
2018-07-12 23:15:04 -04:00
|
|
|
Timeout.timeout(10) do
|
2018-07-07 20:02:27 -04:00
|
|
|
zombies.delete_if do |pid|
|
|
|
|
begin
|
|
|
|
Process.kill(0, pid)
|
|
|
|
false
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end while zombies[0]
|
|
|
|
end
|
|
|
|
assert_predicate zombies, :empty?, 'zombies leftover'
|
|
|
|
|
|
|
|
pids.each do |pid|
|
|
|
|
assert_raise(Errno::ECHILD) { Process.waitpid(pid) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
ensure
|
2018-07-07 21:46:31 -04:00
|
|
|
trap(:CHLD, old) if Signal.list['CHLD']
|
2018-07-07 20:02:27 -04:00
|
|
|
end
|
2018-08-25 17:59:30 -04:00
|
|
|
|
|
|
|
def test_sigwait_fd_unused
|
|
|
|
t = EnvUtil.apply_timeout_scale(0.1)
|
|
|
|
assert_separately([], <<-End)
|
|
|
|
tgt = $$
|
|
|
|
trap(:TERM) { exit(0) }
|
|
|
|
e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
|
|
|
|
term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ]
|
|
|
|
t2 = Thread.new { sleep } # grab sigwait_fd
|
|
|
|
Thread.pass until t2.stop?
|
|
|
|
Thread.new do
|
|
|
|
sleep #{t}
|
|
|
|
t2.kill
|
|
|
|
t2.join
|
|
|
|
end
|
|
|
|
Process.spawn(*term)
|
|
|
|
# last thread remaining, ensure it can react to SIGTERM
|
|
|
|
loop { sleep }
|
|
|
|
End
|
|
|
|
end if Process.respond_to?(:kill) && Process.respond_to?(:daemon)
|
2003-09-04 12:18:59 -04:00
|
|
|
end
|