service_skeleton 0.0.0.34.g4f6fdb0 → 0.0.0.49.g47046b9

Sign up to get free protection for your applications and to get access to all the features.
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