Skip to content

Commit 3f934a5

Browse files
committed
Improve robustness of signal handling
The previous implementation was technically still vulnerable to problems since the signal could be received in between the call to `@context.setup_environment` and `run_hooks`, which could leave the system in a weird state. Solve the problem by creating a utility class `InterruptHandler` which provides an interface for enabling/disabling signal handling so the `HookRunner` can have more control over when/how the signal is handled. Change-Id: I411433169ea1bf23f6f6426e67996d5daf7ae394 Reviewed-on: http://gerrit.causes.com/40326 Tested-by: jenkins <jenkins@causes.com> Reviewed-by: Shane da Silva <shane.dasilva@brigade.com>
1 parent c590b31 commit 3f934a5

File tree

3 files changed

+60
-7
lines changed

3 files changed

+60
-7
lines changed

lib/overcommit.rb

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require 'overcommit/hook_loader/base'
1111
require 'overcommit/hook_loader/built_in_hook_loader'
1212
require 'overcommit/hook_loader/plugin_hook_loader'
13+
require 'overcommit/interrupt_handler'
1314
require 'overcommit/hook_runner'
1415
require 'overcommit/installer'
1516
require 'overcommit/logger'

lib/overcommit/hook_runner.rb

+15-7
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ def initialize(config, logger, context, input)
1818

1919
# Loads and runs the hooks registered for this {HookRunner}.
2020
def run
21-
@context.setup_environment
22-
load_hooks
23-
run_hooks
21+
InterruptHandler.isolate_from_interrupts do
22+
@context.setup_environment
23+
load_hooks
24+
run_hooks
25+
end
2426
ensure
2527
@context.cleanup_environment
2628
end
@@ -76,10 +78,19 @@ def run_hook(hook)
7678
end
7779

7880
begin
81+
# Disable the interrupt handler during individual hook run so that
82+
# Ctrl-C actually stops the current hook from being run, but doesn't
83+
# halt the entire process.
84+
InterruptHandler.disable!
7985
status, output = hook.run
8086
rescue => ex
8187
status = :bad
8288
output = "Hook raised unexpected error\n#{ex.message}"
89+
rescue Interrupt
90+
status = :interrupted
91+
output = 'Hook was interrupted by Ctrl-C; restoring repo state...'
92+
ensure
93+
InterruptHandler.enable!
8394
end
8495

8596
# Want to print the header in the event the result wasn't good so that the
@@ -91,9 +102,6 @@ def run_hook(hook)
91102
print_result(hook, status, output)
92103

93104
status
94-
rescue Interrupt
95-
print_result(hook, :interrupted, nil)
96-
:interrupted
97105
end
98106

99107
def print_header(hook)
@@ -128,7 +136,7 @@ def print_result(hook, status, output)
128136
print_report(output, :bold_error)
129137
when :interrupted
130138
log.error 'INTERRUPTED'
131-
log.bold_error('Hook was interrupted by Ctrl-C; restoring repo state...')
139+
print_report(output, :bold_error)
132140
end
133141
end
134142

lib/overcommit/interrupt_handler.rb

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require 'singleton'
2+
3+
# Provides a handler for interrupt signals (SIGINT), allowing the application to
4+
# finish what it's currently working on.
5+
class InterruptHandler
6+
include Singleton
7+
8+
attr_accessor :isolate_signals, :signal_received
9+
10+
def initialize
11+
self.isolate_signals = false
12+
self.signal_received = false
13+
14+
Signal.trap('INT') do
15+
if isolate_signals
16+
self.signal_received = true
17+
else
18+
raise Interrupt
19+
end
20+
end
21+
end
22+
23+
class << self
24+
def disable!
25+
instance.isolate_signals = false
26+
end
27+
28+
def enable!
29+
instance.isolate_signals = true
30+
end
31+
32+
def isolate_from_interrupts
33+
instance.signal_received = false
34+
instance.isolate_signals = true
35+
result = yield
36+
instance.isolate_signals = false
37+
result
38+
end
39+
40+
def signal_received?
41+
instance.signal_received
42+
end
43+
end
44+
end

0 commit comments

Comments
 (0)