service_skeleton 0.0.0.44.g75d07d7 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +36 -0
- data/.rubocop.yml +9 -1
- data/README.md +264 -149
- data/lib/service_skeleton.rb +20 -186
- data/lib/service_skeleton/config.rb +49 -24
- data/lib/service_skeleton/config_class.rb +16 -0
- data/lib/service_skeleton/config_variable.rb +1 -1
- data/lib/service_skeleton/config_variable/yaml_file.rb +42 -0
- data/lib/service_skeleton/config_variables.rb +28 -23
- data/lib/service_skeleton/error.rb +3 -3
- data/lib/service_skeleton/generator.rb +165 -0
- data/lib/service_skeleton/logging_helpers.rb +3 -3
- data/lib/service_skeleton/metric_method_name.rb +9 -0
- data/lib/service_skeleton/metrics_methods.rb +26 -13
- data/lib/service_skeleton/runner.rb +46 -0
- data/lib/service_skeleton/service_name.rb +20 -0
- data/lib/service_skeleton/signal_manager.rb +202 -0
- data/lib/service_skeleton/signals_methods.rb +15 -0
- data/lib/service_skeleton/ultravisor_children.rb +20 -0
- data/lib/service_skeleton/ultravisor_loggerstash.rb +11 -0
- data/service_skeleton.gemspec +8 -9
- metadata +69 -26
- data/.travis.yml +0 -11
- data/lib/service_skeleton/background_worker.rb +0 -96
- data/lib/service_skeleton/signal_handler.rb +0 -197
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceSkeleton
|
4
|
+
module SignalsMethods
|
5
|
+
def registered_signal_handlers
|
6
|
+
@registered_signal_handlers || []
|
7
|
+
end
|
8
|
+
|
9
|
+
def hook_signal(sigspec, &blk)
|
10
|
+
@registered_signal_handlers ||= []
|
11
|
+
|
12
|
+
@registered_signal_handlers << [sigspec, blk]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ServiceSkeleton
|
4
|
+
module UltravisorChildren
|
5
|
+
def register_ultravisor_children(ultravisor, config:, metrics_registry:)
|
6
|
+
begin
|
7
|
+
ultravisor.add_child(
|
8
|
+
id: self.service_name.to_sym,
|
9
|
+
klass: self,
|
10
|
+
method: :run,
|
11
|
+
args: [config: config, metrics: metrics_registry],
|
12
|
+
access: :unsafe
|
13
|
+
)
|
14
|
+
rescue Ultravisor::InvalidKAMError
|
15
|
+
raise ServiceSkeleton::Error::InvalidServiceClassError,
|
16
|
+
"Class #{self.to_s} does not implement the `run' instance method"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/service_skeleton.gemspec
CHANGED
@@ -24,22 +24,20 @@ Gem::Specification.new do |s|
|
|
24
24
|
common aspects of a system service.
|
25
25
|
EOF
|
26
26
|
|
27
|
-
s.authors = ["Matt Palmer"]
|
28
|
-
s.email = ["
|
27
|
+
s.authors = ["Matt Palmer", "Sam Saffron"]
|
28
|
+
s.email = ["sam.saffron@discourse.org"]
|
29
29
|
s.homepage = "https://github.com/discourse/service_skeleton"
|
30
30
|
|
31
31
|
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(G|spec|Rakefile)/ }
|
32
32
|
|
33
33
|
s.required_ruby_version = ">= 2.5.0"
|
34
34
|
|
35
|
-
s.add_runtime_dependency "frankenstein", "~>
|
36
|
-
s.add_runtime_dependency "loggerstash", "
|
37
|
-
|
38
|
-
# and in fact happily breaks things with no notice, so we're stuck
|
39
|
-
# with hard-coding a specific version to avoid unexpected disaster.
|
40
|
-
s.add_runtime_dependency "prometheus-client", "0.8.0"
|
35
|
+
s.add_runtime_dependency "frankenstein", "~> 2.0"
|
36
|
+
s.add_runtime_dependency "loggerstash", ">= 0.0.9", "< 1"
|
37
|
+
s.add_runtime_dependency "prometheus-client", "~> 2.0"
|
41
38
|
s.add_runtime_dependency "sigdump", "~> 0.2"
|
42
39
|
s.add_runtime_dependency "to_regexp", "~> 0.2"
|
40
|
+
s.add_runtime_dependency "ultravisor", "~> 0.a"
|
43
41
|
|
44
42
|
s.add_development_dependency 'bundler'
|
45
43
|
s.add_development_dependency 'github-release'
|
@@ -47,10 +45,11 @@ Gem::Specification.new do |s|
|
|
47
45
|
s.add_development_dependency 'guard-rspec'
|
48
46
|
s.add_development_dependency 'guard-rubocop'
|
49
47
|
s.add_development_dependency 'rack-test'
|
50
|
-
s.add_development_dependency 'rake'
|
48
|
+
s.add_development_dependency 'rake'
|
51
49
|
s.add_development_dependency 'redcarpet'
|
52
50
|
s.add_development_dependency 'rspec'
|
53
51
|
s.add_development_dependency 'rubocop'
|
52
|
+
s.add_development_dependency 'rubocop-discourse'
|
54
53
|
s.add_development_dependency 'simplecov'
|
55
54
|
s.add_development_dependency 'yard'
|
56
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: service_skeleton
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
|
-
|
8
|
+
- Sam Saffron
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-01-13 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: frankenstein
|
@@ -16,42 +17,48 @@ dependencies:
|
|
16
17
|
requirements:
|
17
18
|
- - "~>"
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
+
version: '2.0'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
25
|
- - "~>"
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
+
version: '2.0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: loggerstash
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- - "
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.0.9
|
35
|
+
- - "<"
|
32
36
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
37
|
+
version: '1'
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
37
41
|
requirements:
|
38
|
-
- - "
|
42
|
+
- - ">="
|
39
43
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
44
|
+
version: 0.0.9
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
41
48
|
- !ruby/object:Gem::Dependency
|
42
49
|
name: prometheus-client
|
43
50
|
requirement: !ruby/object:Gem::Requirement
|
44
51
|
requirements:
|
45
|
-
- -
|
52
|
+
- - "~>"
|
46
53
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
54
|
+
version: '2.0'
|
48
55
|
type: :runtime
|
49
56
|
prerelease: false
|
50
57
|
version_requirements: !ruby/object:Gem::Requirement
|
51
58
|
requirements:
|
52
|
-
- -
|
59
|
+
- - "~>"
|
53
60
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
61
|
+
version: '2.0'
|
55
62
|
- !ruby/object:Gem::Dependency
|
56
63
|
name: sigdump
|
57
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +87,20 @@ dependencies:
|
|
80
87
|
- - "~>"
|
81
88
|
- !ruby/object:Gem::Version
|
82
89
|
version: '0.2'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: ultravisor
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.a
|
97
|
+
type: :runtime
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.a
|
83
104
|
- !ruby/object:Gem::Dependency
|
84
105
|
name: bundler
|
85
106
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,16 +189,16 @@ dependencies:
|
|
168
189
|
name: rake
|
169
190
|
requirement: !ruby/object:Gem::Requirement
|
170
191
|
requirements:
|
171
|
-
- - "
|
192
|
+
- - ">="
|
172
193
|
- !ruby/object:Gem::Version
|
173
|
-
version: '
|
194
|
+
version: '0'
|
174
195
|
type: :development
|
175
196
|
prerelease: false
|
176
197
|
version_requirements: !ruby/object:Gem::Requirement
|
177
198
|
requirements:
|
178
|
-
- - "
|
199
|
+
- - ">="
|
179
200
|
- !ruby/object:Gem::Version
|
180
|
-
version: '
|
201
|
+
version: '0'
|
181
202
|
- !ruby/object:Gem::Dependency
|
182
203
|
name: redcarpet
|
183
204
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,6 +241,20 @@ dependencies:
|
|
220
241
|
- - ">="
|
221
242
|
- !ruby/object:Gem::Version
|
222
243
|
version: '0'
|
244
|
+
- !ruby/object:Gem::Dependency
|
245
|
+
name: rubocop-discourse
|
246
|
+
requirement: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
type: :development
|
252
|
+
prerelease: false
|
253
|
+
version_requirements: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
223
258
|
- !ruby/object:Gem::Dependency
|
224
259
|
name: simplecov
|
225
260
|
requirement: !ruby/object:Gem::Requirement
|
@@ -256,23 +291,23 @@ description: |
|
|
256
291
|
for your services, as well as a collection of helper classes to manage
|
257
292
|
common aspects of a system service.
|
258
293
|
email:
|
259
|
-
-
|
294
|
+
- sam.saffron@discourse.org
|
260
295
|
executables: []
|
261
296
|
extensions: []
|
262
297
|
extra_rdoc_files: []
|
263
298
|
files:
|
264
299
|
- ".editorconfig"
|
300
|
+
- ".github/workflows/ci.yml"
|
265
301
|
- ".gitignore"
|
266
302
|
- ".rubocop.yml"
|
267
|
-
- ".travis.yml"
|
268
303
|
- ".yardopts"
|
269
304
|
- CODE_OF_CONDUCT.md
|
270
305
|
- CONTRIBUTING.md
|
271
306
|
- LICENCE
|
272
307
|
- README.md
|
273
308
|
- lib/service_skeleton.rb
|
274
|
-
- lib/service_skeleton/background_worker.rb
|
275
309
|
- lib/service_skeleton/config.rb
|
310
|
+
- lib/service_skeleton/config_class.rb
|
276
311
|
- lib/service_skeleton/config_variable.rb
|
277
312
|
- lib/service_skeleton/config_variable/boolean.rb
|
278
313
|
- lib/service_skeleton/config_variable/enum.rb
|
@@ -282,17 +317,25 @@ files:
|
|
282
317
|
- lib/service_skeleton/config_variable/path_list.rb
|
283
318
|
- lib/service_skeleton/config_variable/string.rb
|
284
319
|
- lib/service_skeleton/config_variable/url.rb
|
320
|
+
- lib/service_skeleton/config_variable/yaml_file.rb
|
285
321
|
- lib/service_skeleton/config_variables.rb
|
286
322
|
- lib/service_skeleton/error.rb
|
287
323
|
- lib/service_skeleton/filtering_logger.rb
|
324
|
+
- lib/service_skeleton/generator.rb
|
288
325
|
- lib/service_skeleton/logging_helpers.rb
|
326
|
+
- lib/service_skeleton/metric_method_name.rb
|
289
327
|
- lib/service_skeleton/metrics_methods.rb
|
290
|
-
- lib/service_skeleton/
|
328
|
+
- lib/service_skeleton/runner.rb
|
329
|
+
- lib/service_skeleton/service_name.rb
|
330
|
+
- lib/service_skeleton/signal_manager.rb
|
331
|
+
- lib/service_skeleton/signals_methods.rb
|
332
|
+
- lib/service_skeleton/ultravisor_children.rb
|
333
|
+
- lib/service_skeleton/ultravisor_loggerstash.rb
|
291
334
|
- service_skeleton.gemspec
|
292
335
|
homepage: https://github.com/discourse/service_skeleton
|
293
336
|
licenses: []
|
294
337
|
metadata: {}
|
295
|
-
post_install_message:
|
338
|
+
post_install_message:
|
296
339
|
rdoc_options: []
|
297
340
|
require_paths:
|
298
341
|
- lib
|
@@ -303,12 +346,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
303
346
|
version: 2.5.0
|
304
347
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
305
348
|
requirements:
|
306
|
-
- - "
|
349
|
+
- - ">="
|
307
350
|
- !ruby/object:Gem::Version
|
308
|
-
version:
|
351
|
+
version: '0'
|
309
352
|
requirements: []
|
310
|
-
rubygems_version: 3.
|
311
|
-
signing_key:
|
353
|
+
rubygems_version: 3.1.4
|
354
|
+
signing_key:
|
312
355
|
specification_version: 4
|
313
356
|
summary: The bare bones of a service
|
314
357
|
test_files: []
|
data/.travis.yml
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class ServiceSkeleton
|
4
|
-
module BackgroundWorker
|
5
|
-
include ServiceSkeleton::LoggingHelpers
|
6
|
-
|
7
|
-
# async code is a shit to test, and rarely finds bugs anyway, so let's
|
8
|
-
# just
|
9
|
-
#:nocov:
|
10
|
-
# this whole thing.
|
11
|
-
|
12
|
-
# This signal is raised internally if needed to shut down a worker thread.
|
13
|
-
class TerminateBackgroundThread < Exception; end
|
14
|
-
private_constant :TerminateBackgroundThread
|
15
|
-
|
16
|
-
def initialize(*_)
|
17
|
-
@bg_worker_op_mutex = Mutex.new
|
18
|
-
@bg_worker_op_cv = ConditionVariable.new
|
19
|
-
|
20
|
-
begin
|
21
|
-
super
|
22
|
-
rescue ArgumentError => ex
|
23
|
-
if ex.message =~ /wrong number of arguments.*expected 0/
|
24
|
-
super()
|
25
|
-
else
|
26
|
-
raise
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def start!
|
32
|
-
@bg_worker_op_mutex.synchronize do
|
33
|
-
return if @bg_worker_thread
|
34
|
-
|
35
|
-
@bg_worker_thread = Thread.new do
|
36
|
-
Thread.current.name = self.class.to_s
|
37
|
-
|
38
|
-
Thread.handle_interrupt(Exception => :never) do
|
39
|
-
logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} starting" }
|
40
|
-
begin
|
41
|
-
Thread.handle_interrupt(Exception => :immediate) do
|
42
|
-
@bg_worker_op_mutex.synchronize { @bg_worker_op_cv.signal }
|
43
|
-
self.start
|
44
|
-
end
|
45
|
-
rescue TerminateBackgroundThread
|
46
|
-
logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} received magical termination exception" }
|
47
|
-
rescue Exception => ex
|
48
|
-
log_exception(ex) { "Background worker thread #{Thread.current.object_id} received fatal exception" }
|
49
|
-
else
|
50
|
-
logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} terminating" }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} is now done" }
|
54
|
-
end
|
55
|
-
|
56
|
-
@bg_worker_op_cv.wait(@bg_worker_op_mutex) until @bg_worker_thread
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def stop!(force = nil)
|
61
|
-
@bg_worker_op_mutex.synchronize do
|
62
|
-
return if @bg_worker_thread.nil?
|
63
|
-
|
64
|
-
logger.debug("BackgroundWorker(#{self.class})#stop!") { "Terminating worker thread #{@bg_worker_thread.object_id} as requested" }
|
65
|
-
|
66
|
-
if force == :force
|
67
|
-
logger.debug(logloc) { "Forcing termination" }
|
68
|
-
@bg_worker_thread.raise(TerminateBackgroundThread)
|
69
|
-
else
|
70
|
-
logger.debug(logloc) { "Gracefully terminating worker thread" }
|
71
|
-
shutdown
|
72
|
-
end
|
73
|
-
|
74
|
-
begin
|
75
|
-
logger.debug(logloc) { "Waiting for worker thread #{@bg_worker_thread.object_id} to finish itself off" }
|
76
|
-
@bg_worker_thread.join unless @bg_worker_thread == Thread.current
|
77
|
-
rescue TerminateBackgroundThread
|
78
|
-
nil
|
79
|
-
end
|
80
|
-
|
81
|
-
@bg_worker_thread = nil
|
82
|
-
|
83
|
-
logger.debug("BackgroundWorker(#{self.class})#stop!") { "Worker thread #{@bg_worker_thread.object_id} terminated" }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
attr_reader :logger
|
90
|
-
|
91
|
-
def shutdown
|
92
|
-
logger.debug("BackgroundWorker(#{self.class})#stop!") { "Using default shutdown method" }
|
93
|
-
@bg_worker_thread.raise(TerminateBackgroundThread) if @bg_worker_thread
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,197 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "./background_worker"
|
4
|
-
require_relative "./logging_helpers"
|
5
|
-
|
6
|
-
class ServiceSkeleton
|
7
|
-
# Manage signals in a sane and safe manner.
|
8
|
-
#
|
9
|
-
# Signal handling is a shit of a thing. The code that runs when a signal is
|
10
|
-
# triggered can't use mutexes (which are used in all sorts of places you
|
11
|
-
# might not expect, like Logger!) or anything else that might block. This
|
12
|
-
# greatly constrains what you can do inside a signal handler, so the standard
|
13
|
-
# approach is to stuff a character down a pipe, and then have the *real*
|
14
|
-
# signal handling run later.
|
15
|
-
#
|
16
|
-
# Also, there's always the (slim) possibility that something else might have
|
17
|
-
# hooked into a signal we want to receive. Because only a single signal
|
18
|
-
# handler can be active for a given signal at a time, we need to "chain" the
|
19
|
-
# existing handler, by calling the previous signal handler from our signal
|
20
|
-
# handler after we've done what we need to do. This class takes care of
|
21
|
-
# that, too, because it's a legend.
|
22
|
-
#
|
23
|
-
# So that's what this class does: it allows you to specify signals and
|
24
|
-
# associated blocks of code to run, it sets up signal handlers which send
|
25
|
-
# notifications to a background thread and chain correctly, and it manages
|
26
|
-
# the background thread to receive the notifications and execute the
|
27
|
-
# associated blocks of code outside of the context of the signal handler.
|
28
|
-
#
|
29
|
-
class SignalHandler
|
30
|
-
include ServiceSkeleton::LoggingHelpers
|
31
|
-
include ServiceSkeleton::BackgroundWorker
|
32
|
-
|
33
|
-
# Setup a signal handler instance.
|
34
|
-
#
|
35
|
-
# A single signal handler instance can handle up to 256 hooks, potentially
|
36
|
-
# hooking the same signal more than once. Use #hook_signal to register
|
37
|
-
# signal handling callbacks.
|
38
|
-
#
|
39
|
-
# @param logger [Logger] the logger to use for all the interesting information
|
40
|
-
# about what we're up to.
|
41
|
-
#
|
42
|
-
def initialize(logger:, service:, signal_counter:)
|
43
|
-
@logger, @service, @signal_counter = logger, service, signal_counter
|
44
|
-
|
45
|
-
@signal_registry = []
|
46
|
-
|
47
|
-
@handler_install_mutex = Mutex.new
|
48
|
-
|
49
|
-
super
|
50
|
-
end
|
51
|
-
|
52
|
-
#:nocov:
|
53
|
-
|
54
|
-
# Register a callback to be executed on the receipt of a specified signal.
|
55
|
-
#
|
56
|
-
# @param sig [String, Symbol, Integer] the signal to hook into. Anything that
|
57
|
-
# `Signal.trap` will accept is OK by us, too.
|
58
|
-
#
|
59
|
-
# @param blk [Proc] the code to run when the signal is received.
|
60
|
-
#
|
61
|
-
# @return [void]
|
62
|
-
#
|
63
|
-
# @raise [RuntimeError] if you try to create more than 256 signal hooks.
|
64
|
-
#
|
65
|
-
# @raise [ArgumentError] if `sig` isn't recognised as a valid signal
|
66
|
-
# specifier by `Signal.trap`.
|
67
|
-
#
|
68
|
-
def hook_signal(sig, &blk)
|
69
|
-
logger.debug(logloc) { "Hooking signal #{sig}" }
|
70
|
-
|
71
|
-
handler_num = @signal_registry.length
|
72
|
-
|
73
|
-
if handler_num > 255
|
74
|
-
raise RuntimeError,
|
75
|
-
"Signal hook limit reached. Slow down there, pardner"
|
76
|
-
end
|
77
|
-
|
78
|
-
sigspec = { signal: sig, callback: blk }
|
79
|
-
|
80
|
-
@handler_install_mutex.synchronize do
|
81
|
-
if @bg_worker_thread
|
82
|
-
install_handler(sigspec, handler_num)
|
83
|
-
else
|
84
|
-
# If the background thread isn't running yet, the signal handler will
|
85
|
-
# be installed when that is started.
|
86
|
-
logger.debug(logloc) { "Deferring installation of handler for #{sig} (#{handler_num})" }
|
87
|
-
end
|
88
|
-
|
89
|
-
@signal_registry << sigspec
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def start
|
94
|
-
@handler_install_mutex.synchronize do
|
95
|
-
logger.info(logloc) { "Starting signal handler with #{@signal_registry.length} hooks" }
|
96
|
-
|
97
|
-
@r, @w = IO.pipe
|
98
|
-
|
99
|
-
install_signal_handlers
|
100
|
-
end
|
101
|
-
|
102
|
-
loop do
|
103
|
-
begin
|
104
|
-
if ios = IO.select([@r])
|
105
|
-
if ios.first.include?(@r)
|
106
|
-
if ios.first.first.eof?
|
107
|
-
logger.info(logloc) { "Signal pipe closed; shutting down" }
|
108
|
-
break
|
109
|
-
else
|
110
|
-
c = ios.first.first.read_nonblock(1)
|
111
|
-
logger.debug(logloc) { "Received character #{c.inspect} from signal pipe" }
|
112
|
-
handle_signal(c)
|
113
|
-
end
|
114
|
-
else
|
115
|
-
logger.error(logloc) { "Mysterious return from select: #{ios.inspect}" }
|
116
|
-
end
|
117
|
-
end
|
118
|
-
rescue IOError
|
119
|
-
# Something has gone terribly wrong here... bail
|
120
|
-
break
|
121
|
-
rescue StandardError => ex
|
122
|
-
log_exception(ex) { "Exception in select loop" }
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
attr_reader :logger
|
130
|
-
|
131
|
-
# Given a character (presumably) received via the signal pipe, execute the
|
132
|
-
# associated handler.
|
133
|
-
#
|
134
|
-
# @param char [String] a single character, corresponding to an entry in the
|
135
|
-
# signal registry.
|
136
|
-
#
|
137
|
-
# @return [void]
|
138
|
-
#
|
139
|
-
def handle_signal(char)
|
140
|
-
handler = @signal_registry[char.ord]
|
141
|
-
|
142
|
-
if handler
|
143
|
-
logger.debug(logloc) { "#{handler[:signal]} received" }
|
144
|
-
@signal_counter.increment(signal: handler[:signal].to_s)
|
145
|
-
begin
|
146
|
-
handler[:callback].call
|
147
|
-
rescue => ex
|
148
|
-
log_exception(ex) { "Exception in signal handler" }
|
149
|
-
end
|
150
|
-
else
|
151
|
-
logger.error(logloc) { "Unrecognised signal character: #{char.inspect}" }
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def install_signal_handlers
|
156
|
-
@signal_registry.each_with_index do |sigspec, i|
|
157
|
-
install_handler(sigspec, i)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def install_handler(sigspec, i)
|
162
|
-
logger.debug(logloc) { "Installing signal handler for #{sigspec[:signal]}" }
|
163
|
-
chain = nil
|
164
|
-
|
165
|
-
p = ->(*args) do
|
166
|
-
@w.write_nonblock(i.chr) rescue nil
|
167
|
-
chain.call(*args) if chain.respond_to?(:call)
|
168
|
-
end
|
169
|
-
chain = Signal.trap(sigspec[:signal], &p)
|
170
|
-
|
171
|
-
sigspec[:chain] = chain
|
172
|
-
sigspec[:handler] = p
|
173
|
-
end
|
174
|
-
|
175
|
-
def shutdown
|
176
|
-
uninstall_signal_handlers
|
177
|
-
|
178
|
-
@r.close
|
179
|
-
end
|
180
|
-
|
181
|
-
def uninstall_signal_handlers
|
182
|
-
@signal_registry.reverse.each do |sigspec|
|
183
|
-
tmp_sig = Signal.trap(sigspec[:signal], "IGNORE")
|
184
|
-
if tmp_sig == sigspec[:handler]
|
185
|
-
# The current handler is ours, so we can replace
|
186
|
-
# it with the chained handler
|
187
|
-
Signal.trap(sigspec[:signal], sigspec[:chain])
|
188
|
-
else
|
189
|
-
# The current handler *isn't* this one, so we better
|
190
|
-
# put it back, because whoever owns it might get
|
191
|
-
# angry.
|
192
|
-
Signal.trap(sigspec[:signal], tmp_sig)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|