Skip to content

Commit c6bdf79

Browse files
committed
[GR-19220] Improve Signal.trap specs and implementation (#2287)
PullRequest: truffleruby/2481
2 parents 4574e95 + a4d50aa commit c6bdf79

File tree

6 files changed

+111
-25
lines changed

6 files changed

+111
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Compatibility:
6565
* Access to path and mode via `rb_io_t` from C has been changed to improve compatibility for io-console.
6666
* Implemented the `Time.at` `in:` parameter.
6767
* Implemented `Kernel#raise` `cause` parameter.
68+
* Improved compatibility of `Signal.trap` and `Kernel#trap` (#2287, @chrisseaton).
6869

6970
Performance:
7071

spec/ruby/core/kernel/trap_spec.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
require_relative '../../spec_helper'
2-
require_relative 'fixtures/classes'
32

43
describe "Kernel#trap" do
54
it "is a private method" do
65
Kernel.should have_private_instance_method(:trap)
76
end
8-
end
97

10-
describe "Kernel.trap" do
11-
it "needs to be reviewed for spec completeness"
8+
# Behaviour is specified for Signal.trap
129
end

spec/ruby/core/signal/trap_spec.rb

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
require_relative '../../spec_helper'
22

3-
platform_is_not :windows do
4-
describe "Signal.trap" do
3+
describe "Signal.trap" do
4+
platform_is_not :windows do
55
before :each do
66
ScratchPad.clear
77
@proc = -> {}
88
@saved_trap = Signal.trap(:HUP, @proc)
9+
@hup_number = Signal.list["HUP"]
910
end
1011

1112
after :each do
@@ -16,10 +17,11 @@
1617
Signal.trap(:HUP, @saved_trap).should equal(@proc)
1718
end
1819

19-
it "accepts a block in place of a proc/command argument" do
20+
it "accepts a block" do
2021
done = false
2122

22-
Signal.trap(:HUP) do
23+
Signal.trap(:HUP) do |signo|
24+
signo.should == @hup_number
2325
ScratchPad.record :block_trap
2426
done = true
2527
end
@@ -30,6 +32,94 @@
3032
ScratchPad.recorded.should == :block_trap
3133
end
3234

35+
it "accepts a proc" do
36+
done = false
37+
38+
handler = ->(signo) {
39+
signo.should == @hup_number
40+
ScratchPad.record :proc_trap
41+
done = true
42+
}
43+
44+
Signal.trap(:HUP, handler)
45+
46+
Process.kill :HUP, Process.pid
47+
Thread.pass until done
48+
49+
ScratchPad.recorded.should == :proc_trap
50+
end
51+
52+
it "accepts a method" do
53+
done = false
54+
55+
handler_class = Class.new
56+
hup_number = @hup_number
57+
58+
handler_class.define_method :handler_method do |signo|
59+
signo.should == hup_number
60+
ScratchPad.record :method_trap
61+
done = true
62+
end
63+
64+
handler_method = handler_class.new.method(:handler_method)
65+
66+
Signal.trap(:HUP, handler_method)
67+
68+
Process.kill :HUP, Process.pid
69+
Thread.pass until done
70+
71+
ScratchPad.recorded.should == :method_trap
72+
end
73+
74+
it "accepts anything you can call" do
75+
done = false
76+
77+
callable = Object.new
78+
hup_number = @hup_number
79+
80+
callable.singleton_class.define_method :call do |signo|
81+
signo.should == hup_number
82+
ScratchPad.record :callable_trap
83+
done = true
84+
end
85+
86+
Signal.trap(:HUP, callable)
87+
88+
Process.kill :HUP, Process.pid
89+
Thread.pass until done
90+
91+
ScratchPad.recorded.should == :callable_trap
92+
end
93+
94+
it "raises an exception for a non-callable at the point of use" do
95+
not_callable = Object.new
96+
Signal.trap(:HUP, not_callable)
97+
-> {
98+
Process.kill :HUP, Process.pid
99+
loop { Thread.pass }
100+
}.should raise_error(NoMethodError)
101+
end
102+
103+
it "accepts a non-callable that becomes callable when used" do
104+
done = false
105+
106+
late_callable = Object.new
107+
hup_number = @hup_number
108+
109+
Signal.trap(:HUP, late_callable)
110+
111+
late_callable.singleton_class.define_method :call do |signo|
112+
signo.should == hup_number
113+
ScratchPad.record :late_callable_trap
114+
done = true
115+
end
116+
117+
Process.kill :HUP, Process.pid
118+
Thread.pass until done
119+
120+
ScratchPad.recorded.should == :late_callable_trap
121+
end
122+
33123
it "is possible to create a new Thread when the handler runs" do
34124
done = false
35125

@@ -130,14 +220,12 @@
130220
Signal.trap :HUP, @proc
131221
Signal.trap(:HUP, @saved_trap).should equal(@proc)
132222
end
133-
end
134223

135-
describe "Signal.trap" do
136224
# See man 2 signal
137225
%w[KILL STOP].each do |signal|
138226
it "raises ArgumentError or Errno::EINVAL for SIG#{signal}" do
139227
-> {
140-
trap(signal, -> {})
228+
Signal.trap(signal, -> {})
141229
}.should raise_error(StandardError) { |e|
142230
[ArgumentError, Errno::EINVAL].should include(e.class)
143231
e.message.should =~ /Invalid argument|Signal already used by VM or OS/
@@ -152,7 +240,7 @@
152240
end
153241

154242
it "returns 'DEFAULT' for the initial SIGINT handler" do
155-
ruby_exe('print trap(:INT) { abort }').should == 'DEFAULT'
243+
ruby_exe("print Signal.trap(:INT) { abort }").should == 'DEFAULT'
156244
end
157245

158246
it "returns SYSTEM_DEFAULT if passed DEFAULT and no handler was ever set" do
@@ -174,23 +262,22 @@
174262
Signal.signame(status.termsig).should == "PIPE"
175263
end
176264
end
177-
end
178265

179-
describe "Signal.trap" do
180266
describe "the special EXIT signal code" do
181267
it "accepts the EXIT code" do
182-
code = "trap(:EXIT, proc { print 1 })"
268+
code = "Signal.trap(:EXIT, proc { print 1 })"
183269
ruby_exe(code).should == "1"
184270
end
185271

186272
it "runs the proc before at_exit handlers" do
187-
code = "at_exit {print 1}; trap(:EXIT, proc {print 2}); at_exit {print 3}"
273+
code = "at_exit {print 1}; Signal.trap(:EXIT, proc {print 2}); at_exit {print 3}"
188274
ruby_exe(code).should == "231"
189275
end
190276

191277
it "can unset the handler" do
192-
code = "trap(:EXIT, proc { print 1 }); trap(:EXIT, 'DEFAULT')"
278+
code = "Signal.trap(:EXIT, proc { print 1 }); Signal.trap(:EXIT, 'DEFAULT')"
193279
ruby_exe(code).should == ""
194280
end
195281
end
282+
196283
end

spec/tags/core/signal/trap_tags.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
fails:Signal.trap the special EXIT signal code accepts the EXIT code
22
fails:Signal.trap the special EXIT signal code runs the proc before at_exit handlers
3-
slow:Signal.trap the special EXIT signal code accepts the EXIT code
4-
slow:Signal.trap the special EXIT signal code runs the proc before at_exit handlers
5-
slow:Signal.trap the special EXIT signal code can unset the handler
3+
slow:Signal.trap allows to register a handler for all known signals, except reserved signals for which it raises ArgumentError
64
slow:Signal.trap returns 'DEFAULT' for the initial SIGINT handler
75
slow:Signal.trap accepts 'SYSTEM_DEFAULT' and uses the OS handler for SIGPIPE
8-
slow:Signal.trap allows to register a handler for all known signals, except reserved signals for which it raises ArgumentError
6+
slow:Signal.trap the special EXIT signal code can unset the handler

src/main/java/org/truffleruby/core/VMPrimitiveNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ protected boolean watchSignalProc(Object signalString, RubyProc action,
264264
context.getSafepointManager().pauseAllThreadsAndExecuteFromNonRubyThread(
265265
"Handling of signal " + signal,
266266
SafepointPredicate.currentFiberOfThread(context, rootThread),
267-
(rubyThread, currentNode) -> ProcOperations.rootCall(action));
267+
(rubyThread, currentNode) -> ProcOperations.rootCall(action, signal.getNumber()));
268268
});
269269
}
270270

src/main/ruby/truffleruby/core/signal.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,13 @@ def self.trap(signal, handler=nil, &block)
9898
handler = proc { exit }
9999
when String
100100
raise ArgumentError, "Unsupported command '#{handler}'"
101+
when Proc
102+
# handler is already callable
101103
else
102-
unless handler.respond_to? :call
103-
raise ArgumentError, "Handler must respond to #call (was #{handler.class})"
104-
end
104+
underlying_handler = handler
105+
handler = ->(signo) {
106+
underlying_handler.call(signo)
107+
}
105108
end
106109

107110
had_old = @handlers.key?(number)

0 commit comments

Comments
 (0)