service_skeleton 0.0.0.34.g4f6fdb0 → 0.0.0.49.g47046b9

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +260 -145
  3. data/lib/service_skeleton.rb +22 -186
  4. data/lib/service_skeleton/config.rb +57 -30
  5. data/lib/service_skeleton/config_class.rb +16 -0
  6. data/lib/service_skeleton/config_variable.rb +24 -16
  7. data/lib/service_skeleton/config_variable/boolean.rb +21 -0
  8. data/lib/service_skeleton/config_variable/enum.rb +27 -0
  9. data/lib/service_skeleton/config_variable/float.rb +25 -0
  10. data/lib/service_skeleton/config_variable/integer.rb +25 -0
  11. data/lib/service_skeleton/config_variable/kv_list.rb +26 -0
  12. data/lib/service_skeleton/config_variable/path_list.rb +13 -0
  13. data/lib/service_skeleton/config_variable/string.rb +18 -0
  14. data/lib/service_skeleton/config_variable/url.rb +36 -0
  15. data/lib/service_skeleton/config_variable/yaml_file.rb +42 -0
  16. data/lib/service_skeleton/config_variables.rb +49 -82
  17. data/lib/service_skeleton/error.rb +5 -3
  18. data/lib/service_skeleton/filtering_logger.rb +2 -0
  19. data/lib/service_skeleton/generator.rb +165 -0
  20. data/lib/service_skeleton/logging_helpers.rb +5 -3
  21. data/lib/service_skeleton/metric_method_name.rb +9 -0
  22. data/lib/service_skeleton/metrics_methods.rb +28 -13
  23. data/lib/service_skeleton/runner.rb +46 -0
  24. data/lib/service_skeleton/service_name.rb +20 -0
  25. data/lib/service_skeleton/signal_manager.rb +202 -0
  26. data/lib/service_skeleton/signals_methods.rb +15 -0
  27. data/lib/service_skeleton/ultravisor_children.rb +17 -0
  28. data/lib/service_skeleton/ultravisor_loggerstash.rb +11 -0
  29. data/service_skeleton.gemspec +8 -7
  30. metadata +65 -15
  31. data/lib/service_skeleton/background_worker.rb +0 -94
  32. data/lib/service_skeleton/signal_handler.rb +0 -195
@@ -1,94 +0,0 @@
1
- class ServiceSkeleton
2
- module BackgroundWorker
3
- include ServiceSkeleton::LoggingHelpers
4
-
5
- # async code is a shit to test, and rarely finds bugs anyway, so let's
6
- # just
7
- #:nocov:
8
- # this whole thing.
9
-
10
- # This signal is raised internally if needed to shut down a worker thread.
11
- class TerminateBackgroundThread < Exception; end
12
- private_constant :TerminateBackgroundThread
13
-
14
- def initialize(*_)
15
- @bg_worker_op_mutex = Mutex.new
16
- @bg_worker_op_cv = ConditionVariable.new
17
-
18
- begin
19
- super
20
- rescue ArgumentError => ex
21
- if ex.message =~ /wrong number of arguments.*expected 0/
22
- super()
23
- else
24
- raise
25
- end
26
- end
27
- end
28
-
29
- def start!
30
- @bg_worker_op_mutex.synchronize do
31
- return if @bg_worker_thread
32
-
33
- @bg_worker_thread = Thread.new do
34
- Thread.current.name = self.class.to_s
35
-
36
- Thread.handle_interrupt(Exception => :never) do
37
- logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} starting" }
38
- begin
39
- Thread.handle_interrupt(Exception => :immediate) do
40
- @bg_worker_op_mutex.synchronize { @bg_worker_op_cv.signal }
41
- self.start
42
- end
43
- rescue TerminateBackgroundThread
44
- logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} received magical termination exception" }
45
- rescue Exception => ex
46
- log_exception(ex) { "Background worker thread #{Thread.current.object_id} received fatal exception" }
47
- else
48
- logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} terminating" }
49
- end
50
- end
51
- logger.debug("BackgroundWorker(#{self.class})#start!") { "Background worker thread #{Thread.current.object_id} is now done" }
52
- end
53
-
54
- @bg_worker_op_cv.wait(@bg_worker_op_mutex) until @bg_worker_thread
55
- end
56
- end
57
-
58
- def stop!(force = nil)
59
- @bg_worker_op_mutex.synchronize do
60
- return if @bg_worker_thread.nil?
61
-
62
- logger.debug("BackgroundWorker(#{self.class})#stop!") { "Terminating worker thread #{@bg_worker_thread.object_id} as requested" }
63
-
64
- if force == :force
65
- logger.debug(logloc) { "Forcing termination" }
66
- @bg_worker_thread.raise(TerminateBackgroundThread)
67
- else
68
- logger.debug(logloc) { "Gracefully terminating worker thread" }
69
- shutdown
70
- end
71
-
72
- begin
73
- logger.debug(logloc) { "Waiting for worker thread #{@bg_worker_thread.object_id} to finish itself off" }
74
- @bg_worker_thread.join unless @bg_worker_thread == Thread.current
75
- rescue TerminateBackgroundThread
76
- nil
77
- end
78
-
79
- @bg_worker_thread = nil
80
-
81
- logger.debug("BackgroundWorker(#{self.class})#stop!") { "Worker thread #{@bg_worker_thread.object_id} terminated" }
82
- end
83
- end
84
-
85
- private
86
-
87
- attr_reader :logger
88
-
89
- def shutdown
90
- logger.debug("BackgroundWorker(#{self.class})#stop!") { "Using default shutdown method" }
91
- @bg_worker_thread.raise(TerminateBackgroundThread) if @bg_worker_thread
92
- end
93
- end
94
- end
@@ -1,195 +0,0 @@
1
- require_relative "./background_worker"
2
- require_relative "./logging_helpers"
3
-
4
- class ServiceSkeleton
5
- # Manage signals in a sane and safe manner.
6
- #
7
- # Signal handling is a shit of a thing. The code that runs when a signal is
8
- # triggered can't use mutexes (which are used in all sorts of places you
9
- # might not expect, like Logger!) or anything else that might block. This
10
- # greatly constrains what you can do inside a signal handler, so the standard
11
- # approach is to stuff a character down a pipe, and then have the *real*
12
- # signal handling run later.
13
- #
14
- # Also, there's always the (slim) possibility that something else might have
15
- # hooked into a signal we want to receive. Because only a single signal
16
- # handler can be active for a given signal at a time, we need to "chain" the
17
- # existing handler, by calling the previous signal handler from our signal
18
- # handler after we've done what we need to do. This class takes care of
19
- # that, too, because it's a legend.
20
- #
21
- # So that's what this class does: it allows you to specify signals and
22
- # associated blocks of code to run, it sets up signal handlers which send
23
- # notifications to a background thread and chain correctly, and it manages
24
- # the background thread to receive the notifications and execute the
25
- # associated blocks of code outside of the context of the signal handler.
26
- #
27
- class SignalHandler
28
- include ServiceSkeleton::LoggingHelpers
29
- include ServiceSkeleton::BackgroundWorker
30
-
31
- # Setup a signal handler instance.
32
- #
33
- # A single signal handler instance can handle up to 256 hooks, potentially
34
- # hooking the same signal more than once. Use #hook_signal to register
35
- # signal handling callbacks.
36
- #
37
- # @param logger [Logger] the logger to use for all the interesting information
38
- # about what we're up to.
39
- #
40
- def initialize(logger:, service:, signal_counter:)
41
- @logger, @service, @signal_counter = logger, service, signal_counter
42
-
43
- @signal_registry = []
44
-
45
- @handler_install_mutex = Mutex.new
46
-
47
- super
48
- end
49
-
50
- #:nocov:
51
-
52
- # Register a callback to be executed on the receipt of a specified signal.
53
- #
54
- # @param sig [String, Symbol, Integer] the signal to hook into. Anything that
55
- # `Signal.trap` will accept is OK by us, too.
56
- #
57
- # @param blk [Proc] the code to run when the signal is received.
58
- #
59
- # @return [void]
60
- #
61
- # @raise [RuntimeError] if you try to create more than 256 signal hooks.
62
- #
63
- # @raise [ArgumentError] if `sig` isn't recognised as a valid signal
64
- # specifier by `Signal.trap`.
65
- #
66
- def hook_signal(sig, &blk)
67
- logger.debug(logloc) { "Hooking signal #{sig}" }
68
-
69
- handler_num = @signal_registry.length
70
-
71
- if handler_num > 255
72
- raise RuntimeError,
73
- "Signal hook limit reached. Slow down there, pardner"
74
- end
75
-
76
- sigspec = { signal: sig, callback: blk }
77
-
78
- @handler_install_mutex.synchronize do
79
- if @bg_worker_thread
80
- install_handler(sigspec, handler_num)
81
- else
82
- # If the background thread isn't running yet, the signal handler will
83
- # be installed when that is started.
84
- logger.debug(logloc) { "Deferring installation of handler for #{sig} (#{handler_num})" }
85
- end
86
-
87
- @signal_registry << sigspec
88
- end
89
- end
90
-
91
- def start
92
- @handler_install_mutex.synchronize do
93
- logger.info(logloc) { "Starting signal handler with #{@signal_registry.length} hooks" }
94
-
95
- @r, @w = IO.pipe
96
-
97
- install_signal_handlers
98
- end
99
-
100
- loop do
101
- begin
102
- if ios = IO.select([@r])
103
- if ios.first.include?(@r)
104
- if ios.first.first.eof?
105
- logger.info(logloc) { "Signal pipe closed; shutting down" }
106
- break
107
- else
108
- c = ios.first.first.read_nonblock(1)
109
- logger.debug(logloc) { "Received character #{c.inspect} from signal pipe" }
110
- handle_signal(c)
111
- end
112
- else
113
- logger.error(logloc) { "Mysterious return from select: #{ios.inspect}" }
114
- end
115
- end
116
- rescue IOError
117
- # Something has gone terribly wrong here... bail
118
- break
119
- rescue StandardError => ex
120
- log_exception(ex) { "Exception in select loop" }
121
- end
122
- end
123
- end
124
-
125
- private
126
-
127
- attr_reader :logger
128
-
129
- # Given a character (presumably) received via the signal pipe, execute the
130
- # associated handler.
131
- #
132
- # @param char [String] a single character, corresponding to an entry in the
133
- # signal registry.
134
- #
135
- # @return [void]
136
- #
137
- def handle_signal(char)
138
- handler = @signal_registry[char.ord]
139
-
140
- if handler
141
- logger.debug(logloc) { "#{handler[:signal]} received" }
142
- @signal_counter.increment(signal: handler[:signal].to_s)
143
- begin
144
- handler[:callback].call
145
- rescue => ex
146
- log_exception(ex) { "Exception in signal handler" }
147
- end
148
- else
149
- logger.error(logloc) { "Unrecognised signal character: #{char.inspect}" }
150
- end
151
- end
152
-
153
- def install_signal_handlers
154
- @signal_registry.each_with_index do |sigspec, i|
155
- install_handler(sigspec, i)
156
- end
157
- end
158
-
159
- def install_handler(sigspec, i)
160
- logger.debug(logloc) { "Installing signal handler for #{sigspec[:signal]}" }
161
- chain = nil
162
-
163
- p = ->(*args) do
164
- @w.write_nonblock(i.chr) rescue nil
165
- chain.call(*args) if chain.respond_to?(:call)
166
- end
167
- chain = Signal.trap(sigspec[:signal], &p)
168
-
169
- sigspec[:chain] = chain
170
- sigspec[:handler] = p
171
- end
172
-
173
- def shutdown
174
- uninstall_signal_handlers
175
-
176
- @r.close
177
- end
178
-
179
- def uninstall_signal_handlers
180
- @signal_registry.reverse.each do |sigspec|
181
- tmp_sig = Signal.trap(sigspec[:signal], "IGNORE")
182
- if tmp_sig == sigspec[:handler]
183
- # The current handler is ours, so we can replace
184
- # it with the chained handler
185
- Signal.trap(sigspec[:signal], sigspec[:chain])
186
- else
187
- # The current handler *isn't* this one, so we better
188
- # put it back, because whoever owns it might get
189
- # angry.
190
- Signal.trap(sigspec[:signal], tmp_sig)
191
- end
192
- end
193
- end
194
- end
195
- end