service_skeleton 1.0.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +50 -0
  4. data/.gitignore +0 -7
  5. data/.rubocop.yml +11 -1
  6. data/README.md +1 -53
  7. data/lib/service_skeleton/config.rb +20 -13
  8. data/lib/service_skeleton/generator.rb +4 -4
  9. data/lib/service_skeleton/runner.rb +3 -3
  10. data/lib/service_skeleton/ultravisor_children.rb +2 -1
  11. data/lib/service_skeleton/ultravisor_loggerstash.rb +9 -1
  12. data/service_skeleton.gemspec +4 -14
  13. data/ultravisor/.yardopts +1 -0
  14. data/ultravisor/Guardfile +9 -0
  15. data/ultravisor/README.md +404 -0
  16. data/ultravisor/lib/ultravisor.rb +216 -0
  17. data/ultravisor/lib/ultravisor/child.rb +485 -0
  18. data/ultravisor/lib/ultravisor/child/call.rb +21 -0
  19. data/ultravisor/lib/ultravisor/child/call_receiver.rb +14 -0
  20. data/ultravisor/lib/ultravisor/child/cast.rb +16 -0
  21. data/ultravisor/lib/ultravisor/child/cast_receiver.rb +11 -0
  22. data/ultravisor/lib/ultravisor/child/process_cast_call.rb +39 -0
  23. data/ultravisor/lib/ultravisor/error.rb +25 -0
  24. data/ultravisor/lib/ultravisor/logging_helpers.rb +32 -0
  25. data/ultravisor/spec/example_group_methods.rb +19 -0
  26. data/ultravisor/spec/example_methods.rb +8 -0
  27. data/ultravisor/spec/spec_helper.rb +56 -0
  28. data/ultravisor/spec/ultravisor/add_child_spec.rb +79 -0
  29. data/ultravisor/spec/ultravisor/child/call_spec.rb +121 -0
  30. data/ultravisor/spec/ultravisor/child/cast_spec.rb +111 -0
  31. data/ultravisor/spec/ultravisor/child/id_spec.rb +21 -0
  32. data/ultravisor/spec/ultravisor/child/new_spec.rb +152 -0
  33. data/ultravisor/spec/ultravisor/child/restart_delay_spec.rb +40 -0
  34. data/ultravisor/spec/ultravisor/child/restart_spec.rb +70 -0
  35. data/ultravisor/spec/ultravisor/child/run_spec.rb +95 -0
  36. data/ultravisor/spec/ultravisor/child/shutdown_spec.rb +124 -0
  37. data/ultravisor/spec/ultravisor/child/spawn_spec.rb +216 -0
  38. data/ultravisor/spec/ultravisor/child/unsafe_instance_spec.rb +55 -0
  39. data/ultravisor/spec/ultravisor/child/wait_spec.rb +32 -0
  40. data/ultravisor/spec/ultravisor/new_spec.rb +71 -0
  41. data/ultravisor/spec/ultravisor/remove_child_spec.rb +49 -0
  42. data/ultravisor/spec/ultravisor/run_spec.rb +334 -0
  43. data/ultravisor/spec/ultravisor/shutdown_spec.rb +106 -0
  44. metadata +48 -64
  45. data/.travis.yml +0 -11
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../spec_helper"
3
+
4
+ require_relative "../../lib/ultravisor"
5
+
6
+ describe Ultravisor do
7
+ let(:args) { {} }
8
+ let(:ultravisor) { Ultravisor.new(**args) }
9
+ let(:mock_child) { instance_double(Ultravisor::Child) }
10
+
11
+ describe "#remove_child" do
12
+ before(:each) do
13
+ ultravisor.instance_variable_set(:@children, [[:lamb, mock_child]])
14
+ end
15
+
16
+ context "when the ultravisor isn't running" do
17
+ it "removes the child from the list of children" do
18
+ ultravisor.remove_child(:lamb)
19
+
20
+ expect(ultravisor[:lamb]).to be(nil)
21
+ end
22
+
23
+ it "doesn't explode if asked to remove a child that doesn't exist" do
24
+ expect { ultravisor.remove_child(:no_such_child) }.to_not raise_error
25
+ end
26
+ end
27
+
28
+ context "while the ultravisor is running" do
29
+ let(:mock_thread) { instance_double(Thread) }
30
+
31
+ before(:each) do
32
+ allow(mock_child).to receive(:shutdown)
33
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
34
+ end
35
+
36
+ it "shuts down the child" do
37
+ expect(mock_child).to receive(:shutdown)
38
+
39
+ ultravisor.remove_child(:lamb)
40
+ end
41
+
42
+ it "removes the child from the list of children" do
43
+ ultravisor.remove_child(:lamb)
44
+
45
+ expect(ultravisor[:lamb]).to be(nil)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../spec_helper"
3
+
4
+ require_relative "../../lib/ultravisor"
5
+
6
+ describe Ultravisor do
7
+ uses_logger
8
+
9
+ describe "#run" do
10
+ let(:mock_class) { Class.new.tap { |k| k.class_eval { def run; end } } }
11
+ let(:args) { { children: [{ id: :testy, klass: mock_class, method: :run }] } }
12
+ let(:ultravisor) { Ultravisor.new(**args) }
13
+ let(:mock_thread) { instance_double(Thread) }
14
+
15
+ before(:each) do
16
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(:shutdown)
17
+ end
18
+
19
+ context "with no children" do
20
+ let(:args) { {} }
21
+
22
+ it "doesn't start anything" do
23
+ expect(Thread).to_not receive(:new)
24
+
25
+ expect { ultravisor.run }.to_not raise_error
26
+ end
27
+ end
28
+
29
+ context "when already running" do
30
+ before(:each) do
31
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
32
+ end
33
+
34
+ it "raises an exception" do
35
+ expect { ultravisor.run }.to raise_error(Ultravisor::AlreadyRunningError)
36
+ end
37
+ end
38
+
39
+ context "when the event handler gets an unknown event" do
40
+ before(:each) do
41
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return("wassamatta", :shutdown)
42
+ allow(logger).to receive(:error)
43
+ end
44
+
45
+ it "logs an error" do
46
+ expect(logger).to receive(:error).with(match(/^Ultravisor#.*process_events/))
47
+
48
+ ultravisor.run
49
+ end
50
+ end
51
+
52
+ context "with a single child" do
53
+ let(:child) { Ultravisor::Child.new(id: :testy, klass: Object, method: :to_s) }
54
+
55
+ before(:each) do
56
+ allow(child).to receive(:spawn)
57
+ ultravisor.instance_variable_set(:@children, [[child.id, child]])
58
+ end
59
+
60
+ it "spawns a child worker" do
61
+ expect(child).to receive(:spawn)
62
+
63
+ ultravisor.run
64
+ end
65
+
66
+ it "shuts down the child on termination" do
67
+ expect(ultravisor.instance_variable_get(:@children).first.last).to receive(:shutdown)
68
+
69
+ ultravisor.run
70
+ end
71
+
72
+ context "that terminates" do
73
+ before(:each) do
74
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child, :shutdown)
75
+ allow(ultravisor).to receive(:sleep)
76
+ end
77
+
78
+ context "within the limits of its restart policy" do
79
+ it "spawns the child again" do
80
+ expect(child).to receive(:spawn).exactly(:twice)
81
+
82
+ ultravisor.run
83
+ end
84
+
85
+ it "sleeps between restart" do
86
+ expect(ultravisor).to receive(:sleep).with(1)
87
+
88
+ ultravisor.run
89
+ end
90
+ end
91
+
92
+ context "too often for its restart policy" do
93
+ before(:each) do
94
+ allow(child).to receive(:restart?).and_raise(Ultravisor::BlownRestartPolicyError)
95
+ allow(logger).to receive(:error)
96
+ end
97
+
98
+ it "terminates the ultravisor" do
99
+ expect(ultravisor.instance_variable_get(:@queue)).to receive(:<<).with(:shutdown)
100
+
101
+ ultravisor.run
102
+ end
103
+
104
+ it "logs an error" do
105
+ expect(logger).to receive(:error)
106
+
107
+ ultravisor.run
108
+ end
109
+ end
110
+
111
+ context "with a restart_policy delay range" do
112
+ let(:child) { Ultravisor::Child.new(id: :testy, klass: mock_class, method: :run, restart_policy: { delay: 7..12 }) }
113
+
114
+ it "sleeps for a period within the range" do
115
+ expect(ultravisor).to receive(:sleep).with(be_between(7, 12))
116
+
117
+ ultravisor.run
118
+ end
119
+ end
120
+
121
+ context "while we're in the process of shutting down" do
122
+ before(:each) do
123
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop) do
124
+ if ultravisor.instance_variable_get(:@running_thread)
125
+ ultravisor.instance_variable_set(:@running_thread, nil)
126
+ child
127
+ else
128
+ :shutdown
129
+ end
130
+ end
131
+ end
132
+
133
+ it "doesn't respawn the child" do
134
+ expect(child).to receive(:spawn).exactly(:once)
135
+
136
+ ultravisor.run
137
+ end
138
+ end
139
+
140
+ context "with restart: :never" do
141
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :never, method: :run) }
142
+
143
+ it "doesn't respawn the child" do
144
+ expect(child).to receive(:spawn).exactly(:once)
145
+
146
+ ultravisor.run
147
+ end
148
+ end
149
+
150
+ context "with restart: :on_failure" do
151
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :on_failure, method: :run) }
152
+
153
+ it "doesn't respawn the child" do
154
+ expect(child).to receive(:spawn).exactly(:once)
155
+
156
+ ultravisor.run
157
+ end
158
+ end
159
+
160
+ context "with an error" do
161
+ before(:each) do
162
+ allow(logger).to receive(:error)
163
+ ex = Errno::ENOENT.new("I stiiiiiiiill haven't found, what I'm lookin' for")
164
+ ex.set_backtrace(caller)
165
+ allow(child).to receive(:termination_exception).and_return(ex)
166
+ end
167
+
168
+ it "logs the error" do
169
+ expect(logger).to receive(:error).with(match(/:testy/))
170
+
171
+ ultravisor.run
172
+ end
173
+
174
+ it "respawns the child" do
175
+ expect(child).to receive(:spawn).exactly(:twice)
176
+
177
+ ultravisor.run
178
+ end
179
+
180
+ context "with restart: :on_failure" do
181
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :on_failure, method: :run) }
182
+
183
+ it "respawns the child" do
184
+ expect(child).to receive(:spawn).exactly(:twice)
185
+
186
+ ultravisor.run
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ context "with two children" do
194
+ let(:args) do
195
+ {
196
+ children: [
197
+ {
198
+ id: :one,
199
+ klass: Object,
200
+ method: :to_s,
201
+ },
202
+ {
203
+ id: :two,
204
+ klass: Object,
205
+ method: :to_s,
206
+ }
207
+ ]
208
+ }
209
+ end
210
+
211
+ it "starts the children in order of their definition" do
212
+ expect(ultravisor[:one]).to receive(:spawn).ordered
213
+ expect(ultravisor[:two]).to receive(:spawn).ordered
214
+
215
+ ultravisor.run
216
+ end
217
+
218
+ it "shuts the children down in the opposite order" do
219
+ expect(ultravisor[:two]).to receive(:shutdown).ordered
220
+ expect(ultravisor[:one]).to receive(:shutdown).ordered
221
+
222
+ ultravisor.run
223
+ end
224
+ end
225
+
226
+ context "with an all_for_one strategy" do
227
+ let(:args) do
228
+ {
229
+ strategy: :all_for_one,
230
+ children: [
231
+ {
232
+ id: :one,
233
+ klass: Object,
234
+ method: :to_s,
235
+ },
236
+ {
237
+ id: :two,
238
+ klass: Object,
239
+ method: :to_s,
240
+ },
241
+ {
242
+ id: :three,
243
+ klass: Object,
244
+ method: :to_s,
245
+ },
246
+ ]
247
+ }
248
+ end
249
+
250
+ let(:child1) { Ultravisor::Child.new(id: :one, klass: Object, method: :to_s) }
251
+ let(:child2) { Ultravisor::Child.new(id: :two, klass: Object, method: :to_s) }
252
+ let(:child3) { Ultravisor::Child.new(id: :three, klass: Object, method: :to_s) }
253
+
254
+ before(:each) do
255
+ ultravisor.instance_variable_set(:@children, [[:one, child1], [:two, child2], [:three, child3]])
256
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child2, :shutdown)
257
+ allow(ultravisor).to receive(:sleep)
258
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
259
+ end
260
+
261
+ it "shuts down all the other children in reverse order" do
262
+ expect(child3).to receive(:shutdown).ordered
263
+ expect(child1).to receive(:shutdown).ordered
264
+
265
+ ultravisor.__send__(:process_events)
266
+ end
267
+
268
+ it "starts up all children in order" do
269
+ expect(child1).to receive(:spawn).ordered
270
+ expect(child2).to receive(:spawn).ordered
271
+ expect(child3).to receive(:spawn).ordered
272
+
273
+ ultravisor.__send__(:process_events)
274
+ end
275
+ end
276
+
277
+ context "with a rest_for_one strategy" do
278
+ let(:args) do
279
+ {
280
+ strategy: :rest_for_one,
281
+ children: [
282
+ {
283
+ id: :one,
284
+ klass: Object,
285
+ method: :to_s,
286
+ },
287
+ {
288
+ id: :two,
289
+ klass: Object,
290
+ method: :to_s,
291
+ },
292
+ {
293
+ id: :three,
294
+ klass: Object,
295
+ method: :to_s,
296
+ },
297
+ {
298
+ id: :four,
299
+ klass: Object,
300
+ method: :to_s,
301
+ },
302
+ ]
303
+ }
304
+ end
305
+
306
+ let(:child1) { Ultravisor::Child.new(id: :one, klass: Object, method: :to_s) }
307
+ let(:child2) { Ultravisor::Child.new(id: :two, klass: Object, method: :to_s) }
308
+ let(:child3) { Ultravisor::Child.new(id: :three, klass: Object, method: :to_s) }
309
+ let(:child4) { Ultravisor::Child.new(id: :four, klass: Object, method: :to_s) }
310
+
311
+ before(:each) do
312
+ ultravisor.instance_variable_set(:@children, [[:one, child1], [:two, child2], [:three, child3], [:four, child4]])
313
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child2, :shutdown)
314
+ allow(ultravisor).to receive(:sleep)
315
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
316
+ end
317
+
318
+ it "shuts down only the children after the failed one, in reverse order" do
319
+ expect(child4).to receive(:shutdown).ordered
320
+ expect(child3).to receive(:shutdown).ordered
321
+
322
+ ultravisor.__send__(:process_events)
323
+ end
324
+
325
+ it "starts up all the relevant children in order" do
326
+ expect(child2).to receive(:spawn).ordered
327
+ expect(child3).to receive(:spawn).ordered
328
+ expect(child4).to receive(:spawn).ordered
329
+
330
+ ultravisor.__send__(:process_events)
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../spec_helper"
3
+
4
+ require_relative "../../lib/ultravisor"
5
+
6
+ describe Ultravisor do
7
+ let(:args) { {} }
8
+ let(:ultravisor) { Ultravisor.new(**args) }
9
+ let(:mock_thread) { instance_double(Thread) }
10
+
11
+ describe "#shutdown" do
12
+ it "takes the @op_m lock" do
13
+ expect(ultravisor.instance_variable_get(:@op_m)).to receive(:synchronize).and_call_original
14
+
15
+ ultravisor.shutdown
16
+ end
17
+
18
+ context "when the ultravisor isn't running" do
19
+ it "returns itself" do
20
+ expect(ultravisor.shutdown).to eq(ultravisor)
21
+ end
22
+ end
23
+
24
+ context "when the ultravisor is running" do
25
+ before(:each) do
26
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
27
+ allow(ultravisor.instance_variable_get(:@op_cv))
28
+ .to receive(:wait) do
29
+ ultravisor.instance_variable_set(:@running_thread, nil)
30
+ end
31
+ end
32
+
33
+ it "signals the ultravisor to shutdown" do
34
+ expect(ultravisor.instance_variable_get(:@queue)).to receive(:<<).with(:shutdown)
35
+
36
+ ultravisor.shutdown
37
+ end
38
+
39
+ it "waits until the CV is signalled" do
40
+ expect(ultravisor.instance_variable_get(:@op_cv)).to receive(:wait) do |m|
41
+ expect(m).to eq(ultravisor.instance_variable_get(:@op_m))
42
+ ultravisor.instance_variable_set(:@running_thread, nil)
43
+ nil
44
+ end
45
+
46
+ ultravisor.shutdown
47
+ end
48
+
49
+ context "when asked to not wait" do
50
+ it "doesn't wait on the CV" do
51
+ expect(ultravisor.instance_variable_get(:@op_cv)).to_not receive(:wait)
52
+
53
+ ultravisor.shutdown(wait: false)
54
+ end
55
+ end
56
+
57
+ it "returns itself" do
58
+ expect(ultravisor.shutdown).to eq(ultravisor)
59
+ end
60
+
61
+ context "when forced" do
62
+ before(:each) do
63
+ allow(mock_thread).to receive(:kill)
64
+ end
65
+
66
+ it "kills the thread" do
67
+ expect(mock_thread).to receive(:kill)
68
+
69
+ ultravisor.shutdown(force: true)
70
+ end
71
+
72
+ it "tells everyone waiting for the shutdown that the deed is done" do
73
+ expect(ultravisor.instance_variable_get(:@op_cv)).to receive(:broadcast)
74
+
75
+ ultravisor.shutdown(force: true)
76
+ end
77
+
78
+ it "unsets the running thread" do
79
+ ultravisor.shutdown(force: true)
80
+
81
+ expect(ultravisor.instance_variable_get(:@running_thread)).to be(nil)
82
+ end
83
+
84
+ it "doesn't wait on the CV" do
85
+ expect(ultravisor.instance_variable_get(:@op_cv)).to_not receive(:wait)
86
+
87
+ ultravisor.shutdown(force: true)
88
+ end
89
+
90
+ context "with children" do
91
+ let(:child) { Ultravisor::Child.new(id: :one, klass: Object, method: :to_s) }
92
+
93
+ before(:each) do
94
+ ultravisor.instance_variable_set(:@children, [[:child, child]])
95
+ end
96
+
97
+ it "forcibly shuts down the children" do
98
+ expect(child).to receive(:shutdown).with(force: true)
99
+
100
+ ultravisor.shutdown(force: true)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end