process_handler 0.1.1 → 0.1.3
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/salemove/process_handler/pivot_process.rb +64 -17
- data/lib/salemove/process_handler/version.rb +1 -1
- data/spec/process_handler/pivot_process_spec.rb +177 -100
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b3e99c6bdab6b69d12a5a474666c512174360e0
|
4
|
+
data.tar.gz: 3424ff7f14a8c0e219fd4dded8bde82e480f04ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dac9142e911cb9a56f1b877b6a8ec888521effd2201240bbffa58054ac8813a4280d1b8726924dd0a85cb3ecf4aa5653a9408f8f15903f3dccee38057e1af0fb
|
7
|
+
data.tar.gz: 07cd4ae7c681df57c7da57fc428e731404e05ada1666cf7a279ca7e26a8918d74c95b655ea77accdba97255094b9adab2ff59570b75e04445c702533a93b1da9
|
data/Gemfile.lock
CHANGED
@@ -32,19 +32,79 @@ module Salemove
|
|
32
32
|
def spawn(service, blocking: true)
|
33
33
|
@process_monitor.start
|
34
34
|
|
35
|
-
@
|
35
|
+
@service_threads = spawn_queue_threads(service).concat(spawn_tap_threads(service))
|
36
36
|
blocking ? wait_for_monitor : Thread.new { wait_for_monitor }
|
37
37
|
end
|
38
38
|
|
39
|
+
def spawn_queue_threads(service)
|
40
|
+
if service.class.const_defined?(:QUEUE)
|
41
|
+
[ServiceSpawner.spawn(service, @messenger, @exception_notifier)]
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def spawn_tap_threads(service)
|
48
|
+
if service.class.const_defined?(:TAPPED_QUEUES)
|
49
|
+
service.class::TAPPED_QUEUES.map do |queue|
|
50
|
+
spawner = TapServiceSpawner.new(service, @messenger, @exception_notifier)
|
51
|
+
spawner.spawn(queue)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
39
58
|
private
|
40
59
|
|
60
|
+
def self.benchmark(input, &block)
|
61
|
+
type = input[:type] if input.is_a?(Hash)
|
62
|
+
result = nil
|
63
|
+
|
64
|
+
bm = Benchmark.measure { result = block.call }
|
65
|
+
if defined?(Logasm) && PivotProcess.logger.is_a?(Logasm)
|
66
|
+
PivotProcess.logger.debug "Execution time",
|
67
|
+
type: type, real: bm.real, user: bm.utime, system: bm.stime
|
68
|
+
end
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
41
72
|
def wait_for_monitor
|
42
73
|
sleep 1 while @process_monitor.running?
|
43
|
-
@service_thread
|
44
|
-
|
74
|
+
@service_threads.each do |service_thread|
|
75
|
+
service_thread.shutdown
|
76
|
+
service_thread.join
|
77
|
+
end
|
45
78
|
@process_monitor.shutdown
|
46
79
|
end
|
47
80
|
|
81
|
+
class TapServiceSpawner
|
82
|
+
def initialize(service, messenger, exception_notifier)
|
83
|
+
@service = service
|
84
|
+
@messenger = messenger
|
85
|
+
@exception_notifier = exception_notifier
|
86
|
+
end
|
87
|
+
|
88
|
+
def spawn(queue)
|
89
|
+
@messenger.tap_into(queue) do |input|
|
90
|
+
delegate_to_service(input.merge(type: queue))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def delegate_to_service(input)
|
95
|
+
PivotProcess.benchmark(input) { @service.call(input) }
|
96
|
+
rescue => exception
|
97
|
+
handle_exception(exception, input)
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_exception(e, input)
|
101
|
+
PivotProcess.logger.error(e.inspect + "\n" + e.backtrace.join("\n"))
|
102
|
+
if @exception_notifier
|
103
|
+
@exception_notifier.notify_or_ignore(e, cgi_data: ENV.to_hash, parameters: input)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
48
108
|
class ServiceSpawner
|
49
109
|
def self.spawn(service, messenger, exception_notifier)
|
50
110
|
new(service, messenger, exception_notifier).spawn
|
@@ -100,7 +160,7 @@ module Salemove
|
|
100
160
|
end
|
101
161
|
|
102
162
|
def delegate_to_service(input)
|
103
|
-
result = benchmark(input) { @service.call(input) }
|
163
|
+
result = PivotProcess.benchmark(input) { @service.call(input) }
|
104
164
|
PivotProcess.logger.info "Result: #{result.inspect}"
|
105
165
|
result
|
106
166
|
end
|
@@ -112,19 +172,6 @@ module Salemove
|
|
112
172
|
end
|
113
173
|
{ success: false, error: e.message }
|
114
174
|
end
|
115
|
-
|
116
|
-
def benchmark(input, &block)
|
117
|
-
type = input[:type] if input.is_a?(Hash)
|
118
|
-
result = nil
|
119
|
-
|
120
|
-
bm = Benchmark.measure { result = block.call }
|
121
|
-
if defined?(Logasm) && PivotProcess.logger.is_a?(Logasm)
|
122
|
-
PivotProcess.logger.debug "Execution time",
|
123
|
-
type: type, real: bm.real, user: bm.utime, system: bm.stime
|
124
|
-
end
|
125
|
-
|
126
|
-
result
|
127
|
-
end
|
128
175
|
end
|
129
176
|
end
|
130
177
|
end
|
@@ -2,27 +2,15 @@ require 'logasm'
|
|
2
2
|
require 'spec_helper'
|
3
3
|
require 'salemove/process_handler/pivot_process'
|
4
4
|
|
5
|
-
class ResultService
|
6
|
-
QUEUE = 'Dummy'
|
7
|
-
end
|
8
|
-
|
9
5
|
describe ProcessHandler::PivotProcess do
|
10
6
|
let(:monitor) { double('Monitor') }
|
11
7
|
let(:messenger) { double('Messenger') }
|
12
8
|
let(:handler) { double('Handler') }
|
13
9
|
let(:thread) { double('Thread') }
|
14
|
-
|
15
|
-
subject { process.spawn(service) }
|
16
|
-
let(:service) { ResultService.new }
|
17
|
-
|
18
10
|
let(:process) { ProcessHandler::PivotProcess.new(messenger, process_params) }
|
19
11
|
let(:process_params) {{ process_monitor: monitor , notifier_factory: notifier_factory, env: 'test' }}
|
20
12
|
let(:notifier_factory) { double('NotifierFactory') }
|
21
13
|
let(:responder) { double(shutdown: true, join: true) }
|
22
|
-
|
23
|
-
let(:input) {{}}
|
24
|
-
let(:result) { {success: true, result: 'RESULT'} }
|
25
|
-
|
26
14
|
let(:logger) { Logasm.new([]) }
|
27
15
|
|
28
16
|
def expect_monitor_to_behave
|
@@ -31,155 +19,244 @@ describe ProcessHandler::PivotProcess do
|
|
31
19
|
expect(monitor).to receive(:shutdown)
|
32
20
|
end
|
33
21
|
|
34
|
-
def expect_message
|
35
|
-
expect(messenger).to receive(:respond_to) {|destination, &callback|
|
36
|
-
callback.call(input, handler)
|
37
|
-
}.and_return(responder)
|
38
|
-
end
|
39
|
-
|
40
|
-
def expect_handler_thread_to_behave
|
41
|
-
allow(handler).to receive(:success) { thread }
|
42
|
-
allow(handler).to receive(:error) { thread }
|
43
|
-
expect(responder).to receive(:shutdown)
|
44
|
-
expect(responder).to receive(:join)
|
45
|
-
end
|
46
|
-
|
47
22
|
before do
|
48
23
|
ProcessHandler::PivotProcess.logger = logger
|
49
24
|
allow(notifier_factory).to receive(:get_notifier) { nil }
|
50
25
|
expect_monitor_to_behave
|
51
|
-
expect_message
|
52
|
-
expect_handler_thread_to_behave
|
53
|
-
allow(service).to receive(:call).with(input) { result }
|
54
26
|
end
|
55
27
|
|
56
|
-
describe 'when service responds correctly' do
|
57
28
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
subject()
|
29
|
+
describe 'responding services' do
|
30
|
+
class ResultService
|
31
|
+
QUEUE = 'Dummy'
|
62
32
|
end
|
63
33
|
|
64
|
-
|
34
|
+
subject { process.spawn(service) }
|
35
|
+
let(:service) { ResultService.new }
|
65
36
|
|
66
|
-
|
67
|
-
let(:result) { {
|
37
|
+
let(:input) {{}}
|
38
|
+
let(:result) { {success: true, result: 'RESULT'} }
|
68
39
|
|
69
|
-
|
70
|
-
|
40
|
+
def expect_handler_thread_to_behave
|
41
|
+
allow(handler).to receive(:success) { thread }
|
42
|
+
allow(handler).to receive(:error) { thread }
|
43
|
+
expect(responder).to receive(:shutdown)
|
44
|
+
expect(responder).to receive(:join)
|
71
45
|
end
|
72
46
|
|
73
|
-
it 'acks the message properly' do
|
74
|
-
expect(handler).to receive(:error).with(result)
|
75
|
-
subject()
|
76
|
-
end
|
77
|
-
end
|
78
47
|
|
79
|
-
|
48
|
+
def expect_message
|
49
|
+
expect(messenger).to receive(:respond_to) {|destination, &callback|
|
50
|
+
callback.call(input, handler)
|
51
|
+
}.and_return(responder)
|
52
|
+
end
|
80
53
|
|
81
|
-
|
82
|
-
|
83
|
-
|
54
|
+
before do
|
55
|
+
expect_message
|
56
|
+
expect_handler_thread_to_behave
|
57
|
+
allow(service).to receive(:call).with(input) { result }
|
84
58
|
end
|
85
59
|
|
86
|
-
describe '
|
60
|
+
describe 'when service responds correctly' do
|
87
61
|
|
88
|
-
|
62
|
+
it 'can be executed with logger' do
|
63
|
+
expect(handler).to receive(:success).with(result)
|
64
|
+
expect(service).to receive(:call).with(input)
|
65
|
+
subject()
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'when service responds with an error' do
|
71
|
+
let(:result) { { success: false, error: 'hey' } }
|
89
72
|
|
90
73
|
before do
|
91
|
-
|
74
|
+
expect(service).to receive(:call).with(input) { result }
|
92
75
|
end
|
93
76
|
|
94
|
-
it '
|
95
|
-
expect(
|
77
|
+
it 'acks the message properly' do
|
78
|
+
expect(handler).to receive(:error).with(result)
|
96
79
|
subject()
|
97
80
|
end
|
98
81
|
end
|
99
82
|
|
100
|
-
|
83
|
+
shared_examples 'an error_handler' do
|
101
84
|
|
102
|
-
|
85
|
+
it 'logs error' do
|
86
|
+
expect(logger).to receive(:error)
|
87
|
+
subject()
|
88
|
+
end
|
103
89
|
|
104
|
-
|
105
|
-
let(:exception) { "what an unexpected exception!" }
|
90
|
+
describe 'with exception_notifier' do
|
106
91
|
|
107
|
-
|
108
|
-
expect(service).to receive(:call).with(input) { raise exception }
|
109
|
-
end
|
92
|
+
let(:exception_notifier) { double('Airbrake') }
|
110
93
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
94
|
+
before do
|
95
|
+
allow(notifier_factory).to receive(:get_notifier) { exception_notifier }
|
96
|
+
end
|
115
97
|
|
116
|
-
|
98
|
+
it 'triggers exception_notifier' do
|
99
|
+
expect(exception_notifier).to receive(:notify_or_ignore)
|
100
|
+
subject()
|
101
|
+
end
|
102
|
+
end
|
117
103
|
|
118
|
-
|
104
|
+
end
|
119
105
|
|
120
|
-
|
106
|
+
describe 'when service raises exception' do
|
121
107
|
|
122
|
-
|
123
|
-
|
108
|
+
let(:result) { { success: false, error: exception } }
|
109
|
+
let(:exception) { "what an unexpected exception!" }
|
124
110
|
|
125
|
-
|
126
|
-
|
127
|
-
|
111
|
+
before do
|
112
|
+
expect(service).to receive(:call).with(input) { raise exception }
|
113
|
+
end
|
128
114
|
|
129
|
-
|
130
|
-
|
131
|
-
|
115
|
+
it 'acks the message properly' do
|
116
|
+
expect(handler).to receive(:error).with(result)
|
117
|
+
subject()
|
118
|
+
end
|
132
119
|
|
133
|
-
|
120
|
+
it_behaves_like 'an error_handler'
|
134
121
|
|
135
|
-
|
122
|
+
end
|
136
123
|
|
137
|
-
|
138
|
-
let(:result) { double }
|
124
|
+
describe 'when exception raises after service call' do
|
139
125
|
|
140
|
-
|
141
|
-
let(:
|
126
|
+
let(:result) { { success: false, output: exception } }
|
127
|
+
let(:exception) { "no no no ... no inspect for you!" }
|
142
128
|
|
143
129
|
before do
|
144
|
-
|
145
|
-
allow(result).to receive(:value) { value }
|
130
|
+
expect(result).to receive(:inspect) { raise exception }
|
146
131
|
end
|
147
132
|
|
148
|
-
it '
|
149
|
-
expect(handler).to receive(:success).with(value)
|
133
|
+
it 'still acks the message properly' do
|
150
134
|
subject()
|
151
135
|
end
|
136
|
+
|
137
|
+
it_behaves_like 'an error_handler'
|
138
|
+
|
152
139
|
end
|
153
140
|
|
154
|
-
|
155
|
-
let(:
|
141
|
+
describe 'when result is fulfillable' do
|
142
|
+
let(:result) { double }
|
156
143
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
144
|
+
context 'and its already fulfilled' do
|
145
|
+
let(:value) { { success: true, output: { result: 'R'} } }
|
146
|
+
|
147
|
+
before do
|
161
148
|
allow(result).to receive(:fulfilled?) { true }
|
162
149
|
allow(result).to receive(:value) { value }
|
163
150
|
end
|
151
|
+
|
152
|
+
it 'responds immediately' do
|
153
|
+
expect(handler).to receive(:success).with(value)
|
154
|
+
subject()
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'and its fulfilled later' do
|
159
|
+
let(:value) { { success: true, output: { result: 'R'} } }
|
160
|
+
|
161
|
+
before do
|
162
|
+
allow(result).to receive(:fulfilled?) { false }
|
163
|
+
Thread.new do
|
164
|
+
sleep 0.005
|
165
|
+
allow(result).to receive(:fulfilled?) { true }
|
166
|
+
allow(result).to receive(:value) { value }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'responds when fulfilled' do
|
171
|
+
expect(handler).to receive(:success).with(value)
|
172
|
+
subject()
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'and its never fulfilled' do
|
177
|
+
before do
|
178
|
+
allow(result).to receive(:fulfilled?) { false }
|
179
|
+
allow(result).to receive(:timeout) { 0.001 }
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'responds with timeout error' do
|
183
|
+
expect(handler).to receive(:error).with(success: false, error: "Fulfillable response was not fulfilled")
|
184
|
+
subject
|
185
|
+
end
|
164
186
|
end
|
187
|
+
end
|
188
|
+
end
|
165
189
|
|
166
|
-
|
167
|
-
|
190
|
+
describe 'tapping services' do
|
191
|
+
class TappingService
|
192
|
+
TAPPED_QUEUES = [
|
193
|
+
'one',
|
194
|
+
'two'
|
195
|
+
]
|
196
|
+
end
|
197
|
+
|
198
|
+
subject { process.spawn(service) }
|
199
|
+
let(:service) { TappingService.new }
|
200
|
+
let(:tap_count) { TappingService::TAPPED_QUEUES.count }
|
201
|
+
|
202
|
+
let(:input) {{}}
|
203
|
+
|
204
|
+
def expect_tap_into
|
205
|
+
expect(messenger).to receive(:tap_into) do |destination, &callback|
|
206
|
+
callback.call(input)
|
207
|
+
end
|
208
|
+
.exactly(tap_count).times
|
209
|
+
.and_return(responder)
|
210
|
+
end
|
211
|
+
|
212
|
+
before do
|
213
|
+
expect_tap_into
|
214
|
+
expect(responder).to receive(:shutdown)
|
215
|
+
expect(responder).to receive(:join)
|
216
|
+
allow(service).to receive(:call).with(input)
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
describe 'when service handles the input correctly' do
|
221
|
+
it 'can be executed' do
|
222
|
+
expect(service).to receive(:call).with(input.merge(type: 'one'))
|
223
|
+
expect(service).to receive(:call).with(input.merge(type: 'two'))
|
168
224
|
subject()
|
169
225
|
end
|
170
226
|
end
|
171
227
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
228
|
+
shared_examples 'an error_handler' do
|
229
|
+
it 'logs error' do
|
230
|
+
expect(logger).to receive(:error)
|
231
|
+
subject()
|
176
232
|
end
|
177
233
|
|
178
|
-
|
179
|
-
|
180
|
-
|
234
|
+
describe 'with exception_notifier' do
|
235
|
+
|
236
|
+
let(:exception_notifier) { double('Airbrake') }
|
237
|
+
|
238
|
+
before do
|
239
|
+
allow(notifier_factory).to receive(:get_notifier) { exception_notifier }
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'triggers exception_notifier' do
|
243
|
+
expect(exception_notifier).to receive(:notify_or_ignore)
|
244
|
+
subject()
|
245
|
+
end
|
181
246
|
end
|
247
|
+
|
182
248
|
end
|
183
|
-
end
|
184
249
|
|
250
|
+
describe 'when service raises exception' do
|
251
|
+
let(:exception) { "what an unexpected exception!" }
|
252
|
+
|
253
|
+
before do
|
254
|
+
expect(service).to receive(:call).with(input.merge(type: 'one')) {}
|
255
|
+
expect(service).to receive(:call).with(input.merge(type: 'two')) { raise exception }
|
256
|
+
end
|
257
|
+
|
258
|
+
it_behaves_like 'an error_handler'
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
185
262
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_handler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Indrek Juhkam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: airbrake
|