service_skeleton 0.0.0.44.g75d07d7 → 0.0.0.48.g4a40599

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,17 @@
1
+ module ServiceSkeleton
2
+ module UltravisorChildren
3
+ def register_ultravisor_children(ultravisor, config:, metrics_registry:)
4
+ begin
5
+ ultravisor.add_child(
6
+ id: self.service_name.to_sym,
7
+ klass: self,
8
+ method: :run,
9
+ args: [config: config, metrics: metrics_registry]
10
+ )
11
+ rescue Ultravisor::InvalidKAMError
12
+ raise ServiceSkeleton::Error::InvalidServiceClassError,
13
+ "Class #{self.to_s} does not implement the `run' instance method"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ServiceSkeleton
4
+ module UltravisorLoggerstash
5
+ def logstash_writer
6
+ #:nocov:
7
+ @ultravisor[:logstash_writer].unsafe_instance
8
+ #:nocov:
9
+ end
10
+ end
11
+ end
@@ -32,14 +32,12 @@ Gem::Specification.new do |s|
32
32
 
33
33
  s.required_ruby_version = ">= 2.5.0"
34
34
 
35
- s.add_runtime_dependency "frankenstein", "~> 1.2"
36
- s.add_runtime_dependency "loggerstash", "~> 0.0"
37
- # prometheus-client provides no guaranteed backwards compatibility,
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'
@@ -50,7 +48,8 @@ Gem::Specification.new do |s|
50
48
  s.add_development_dependency 'rake', "~> 12.0"
51
49
  s.add_development_dependency 'redcarpet'
52
50
  s.add_development_dependency 'rspec'
53
- s.add_development_dependency 'rubocop'
51
+ s.add_development_dependency 'rubocop', "~> 0.79"
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,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_skeleton
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.44.g75d07d7
4
+ version: 0.0.0.48.g4a40599
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-26 00:00:00.000000000 Z
11
+ date: 2020-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: frankenstein
@@ -16,42 +16,48 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.2'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.2'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: loggerstash
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.0'
33
+ version: 0.0.9
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '1'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.0.9
44
+ - - "<"
39
45
  - !ruby/object:Gem::Version
40
- version: '0.0'
46
+ version: '1'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: prometheus-client
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
- - - '='
51
+ - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: 0.8.0
53
+ version: '2.0'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
- - - '='
58
+ - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: 0.8.0
60
+ version: '2.0'
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: sigdump
57
63
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +86,20 @@ dependencies:
80
86
  - - "~>"
81
87
  - !ruby/object:Gem::Version
82
88
  version: '0.2'
89
+ - !ruby/object:Gem::Dependency
90
+ name: ultravisor
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.a
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.a
83
103
  - !ruby/object:Gem::Dependency
84
104
  name: bundler
85
105
  requirement: !ruby/object:Gem::Requirement
@@ -208,6 +228,20 @@ dependencies:
208
228
  version: '0'
209
229
  - !ruby/object:Gem::Dependency
210
230
  name: rubocop
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - "~>"
234
+ - !ruby/object:Gem::Version
235
+ version: '0.79'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: '0.79'
243
+ - !ruby/object:Gem::Dependency
244
+ name: rubocop-discourse
211
245
  requirement: !ruby/object:Gem::Requirement
212
246
  requirements:
213
247
  - - ">="
@@ -271,8 +305,8 @@ files:
271
305
  - LICENCE
272
306
  - README.md
273
307
  - lib/service_skeleton.rb
274
- - lib/service_skeleton/background_worker.rb
275
308
  - lib/service_skeleton/config.rb
309
+ - lib/service_skeleton/config_class.rb
276
310
  - lib/service_skeleton/config_variable.rb
277
311
  - lib/service_skeleton/config_variable/boolean.rb
278
312
  - lib/service_skeleton/config_variable/enum.rb
@@ -282,12 +316,20 @@ files:
282
316
  - lib/service_skeleton/config_variable/path_list.rb
283
317
  - lib/service_skeleton/config_variable/string.rb
284
318
  - lib/service_skeleton/config_variable/url.rb
319
+ - lib/service_skeleton/config_variable/yaml_file.rb
285
320
  - lib/service_skeleton/config_variables.rb
286
321
  - lib/service_skeleton/error.rb
287
322
  - lib/service_skeleton/filtering_logger.rb
323
+ - lib/service_skeleton/generator.rb
288
324
  - lib/service_skeleton/logging_helpers.rb
325
+ - lib/service_skeleton/metric_method_name.rb
289
326
  - lib/service_skeleton/metrics_methods.rb
290
- - lib/service_skeleton/signal_handler.rb
327
+ - lib/service_skeleton/runner.rb
328
+ - lib/service_skeleton/service_name.rb
329
+ - lib/service_skeleton/signal_manager.rb
330
+ - lib/service_skeleton/signals_methods.rb
331
+ - lib/service_skeleton/ultravisor_children.rb
332
+ - lib/service_skeleton/ultravisor_loggerstash.rb
291
333
  - service_skeleton.gemspec
292
334
  homepage: https://github.com/discourse/service_skeleton
293
335
  licenses: []
@@ -307,7 +349,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
307
349
  - !ruby/object:Gem::Version
308
350
  version: 1.3.1
309
351
  requirements: []
310
- rubygems_version: 3.0.1
352
+ rubygems_version: 3.0.3
311
353
  signing_key:
312
354
  specification_version: 4
313
355
  summary: The bare bones of a service
@@ -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