service_skeleton 0.0.0.44.g75d07d7 → 0.0.0.48.g4a40599

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.
@@ -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