bbk-app 1.0.0.72899
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +177 -0
- data/README.md +38 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/bbk/app/dispatcher/message.rb +47 -0
- data/lib/bbk/app/dispatcher/message_stream.rb +42 -0
- data/lib/bbk/app/dispatcher/queue_stream_strategy.rb +54 -0
- data/lib/bbk/app/dispatcher/result.rb +20 -0
- data/lib/bbk/app/dispatcher/route.rb +37 -0
- data/lib/bbk/app/dispatcher.rb +210 -0
- data/lib/bbk/app/factory.rb +26 -0
- data/lib/bbk/app/handler.rb +69 -0
- data/lib/bbk/app/matchers/base.rb +50 -0
- data/lib/bbk/app/matchers/delivery_info.rb +35 -0
- data/lib/bbk/app/matchers/full.rb +41 -0
- data/lib/bbk/app/matchers/headers.rb +23 -0
- data/lib/bbk/app/matchers/payload.rb +23 -0
- data/lib/bbk/app/matchers.rb +28 -0
- data/lib/bbk/app/middlewares/active_record_pool.rb +21 -0
- data/lib/bbk/app/middlewares/base.rb +20 -0
- data/lib/bbk/app/middlewares/from_block.rb +26 -0
- data/lib/bbk/app/middlewares/self_killer.rb +66 -0
- data/lib/bbk/app/middlewares/watchdog.rb +78 -0
- data/lib/bbk/app/middlewares.rb +12 -0
- data/lib/bbk/app/processors/base.rb +46 -0
- data/lib/bbk/app/processors/ping.rb +26 -0
- data/lib/bbk/app/processors/pong.rb +16 -0
- data/lib/bbk/app/processors.rb +3 -0
- data/lib/bbk/app/proxy_logger.rb +42 -0
- data/lib/bbk/app/thread_pool.rb +75 -0
- data/lib/bbk/app/version.rb +8 -0
- data/lib/bbk/app.rb +23 -0
- data/sig/bbk/app/callable.rbs +3 -0
- data/sig/bbk/app/dispatcher/message.rbs +33 -0
- data/sig/bbk/app/dispatcher/message_stream.rbs +15 -0
- data/sig/bbk/app/dispatcher/queue_stream_strategy.rbs +12 -0
- data/sig/bbk/app/dispatcher/result.rbs +12 -0
- data/sig/bbk/app/dispatcher/route.rbs +18 -0
- data/sig/bbk/app/dispatcher/stream_strategy.rbs +13 -0
- data/sig/bbk/app/dispatcher.rbs +62 -0
- data/sig/bbk/app/factory.rbs +21 -0
- data/sig/bbk/app/handler.rbs +19 -0
- data/sig/bbk/app/matchers.rbs +12 -0
- data/sig/bbk/app/middlewares/self_killer.rbs +26 -0
- data/sig/bbk/app/middlewares/watchdog.rbs +40 -0
- data/sig/bbk/app/processors/base.rbs +21 -0
- metadata +327 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b47e943b8f8075f53603a17fdbe11c1c71de2dd330e9a338f7ab5f4901602ecc
|
4
|
+
data.tar.gz: f0f7ebd29ed819ebc6e2e0d7999ff138989bd637bc382bb8be5c42397a80dc32
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 261f9ce6914823596cd18afc01ff4ec8717c472ba5b956e3ee3fcfdde69c21bdee6193ff48d959aeab6211fdda8e44e4f47e21a7c2f2be2990377a6f5129cd29
|
7
|
+
data.tar.gz: ca5a3164cc571ba52c9d04b704ab199b700027ce65c935b08e220cb364cd047f27b6613f12de152a7b26c8d989f1cf1faa7b7307ae8bc570ea7978e235f4c3fb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bbk-app (1.0.0.72899)
|
5
|
+
activesupport
|
6
|
+
bbk-utils (> 1.0.1)
|
7
|
+
timeouter
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (6.1.4.4)
|
13
|
+
activesupport (= 6.1.4.4)
|
14
|
+
activerecord (6.1.4.4)
|
15
|
+
activemodel (= 6.1.4.4)
|
16
|
+
activesupport (= 6.1.4.4)
|
17
|
+
activesupport (6.1.4.4)
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
+
i18n (>= 1.6, < 2)
|
20
|
+
minitest (>= 5.1)
|
21
|
+
tzinfo (~> 2.0)
|
22
|
+
zeitwerk (~> 2.3)
|
23
|
+
addressable (2.8.0)
|
24
|
+
public_suffix (>= 2.0.2, < 5.0)
|
25
|
+
amq-protocol (2.3.2)
|
26
|
+
ansi (1.5.0)
|
27
|
+
ast (2.4.2)
|
28
|
+
axiom-types (0.1.1)
|
29
|
+
descendants_tracker (~> 0.0.4)
|
30
|
+
ice_nine (~> 0.11.0)
|
31
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
32
|
+
bbk-utils (1.0.1.72735)
|
33
|
+
activesupport (~> 6.0)
|
34
|
+
russian
|
35
|
+
bunny (2.19.0)
|
36
|
+
amq-protocol (~> 2.3, >= 2.3.1)
|
37
|
+
sorted_set (~> 1, >= 1.0.2)
|
38
|
+
bunny-mock (1.7.0)
|
39
|
+
bunny (>= 1.7)
|
40
|
+
byebug (11.1.3)
|
41
|
+
coercible (1.0.0)
|
42
|
+
descendants_tracker (~> 0.0.1)
|
43
|
+
concurrent-ruby (1.1.9)
|
44
|
+
database_cleaner (1.99.0)
|
45
|
+
descendants_tracker (0.0.4)
|
46
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
47
|
+
diff-lcs (1.5.0)
|
48
|
+
docile (1.4.0)
|
49
|
+
equalizer (0.0.11)
|
50
|
+
erubis (2.7.0)
|
51
|
+
faker (2.19.0)
|
52
|
+
i18n (>= 1.6, < 2)
|
53
|
+
flay (2.12.1)
|
54
|
+
erubis (~> 2.7.0)
|
55
|
+
path_expander (~> 1.0)
|
56
|
+
ruby_parser (~> 3.0)
|
57
|
+
sexp_processor (~> 4.0)
|
58
|
+
flog (4.6.4)
|
59
|
+
path_expander (~> 1.0)
|
60
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
61
|
+
sexp_processor (~> 4.8)
|
62
|
+
i18n (1.8.11)
|
63
|
+
concurrent-ruby (~> 1.0)
|
64
|
+
ice_nine (0.11.2)
|
65
|
+
kwalify (0.7.2)
|
66
|
+
launchy (2.5.0)
|
67
|
+
addressable (~> 2.7)
|
68
|
+
minitest (5.15.0)
|
69
|
+
parallel (1.21.0)
|
70
|
+
parser (3.0.3.2)
|
71
|
+
ast (~> 2.4.1)
|
72
|
+
path_expander (1.1.0)
|
73
|
+
public_suffix (4.0.6)
|
74
|
+
rainbow (3.1.1)
|
75
|
+
rake (12.3.3)
|
76
|
+
rbtree (0.4.4)
|
77
|
+
reek (6.0.6)
|
78
|
+
kwalify (~> 0.7.0)
|
79
|
+
parser (~> 3.0.0)
|
80
|
+
rainbow (>= 2.0, < 4.0)
|
81
|
+
regexp_parser (2.2.0)
|
82
|
+
rexml (3.2.5)
|
83
|
+
rspec (3.10.0)
|
84
|
+
rspec-core (~> 3.10.0)
|
85
|
+
rspec-expectations (~> 3.10.0)
|
86
|
+
rspec-mocks (~> 3.10.0)
|
87
|
+
rspec-core (3.10.1)
|
88
|
+
rspec-support (~> 3.10.0)
|
89
|
+
rspec-expectations (3.10.1)
|
90
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
91
|
+
rspec-support (~> 3.10.0)
|
92
|
+
rspec-mocks (3.10.2)
|
93
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
94
|
+
rspec-support (~> 3.10.0)
|
95
|
+
rspec-support (3.10.3)
|
96
|
+
rspec_junit_formatter (0.5.1)
|
97
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
98
|
+
rubocop (1.24.1)
|
99
|
+
parallel (~> 1.10)
|
100
|
+
parser (>= 3.0.0.0)
|
101
|
+
rainbow (>= 2.2.2, < 4.0)
|
102
|
+
regexp_parser (>= 1.8, < 3.0)
|
103
|
+
rexml
|
104
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
105
|
+
ruby-progressbar (~> 1.7)
|
106
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
107
|
+
rubocop-ast (1.15.1)
|
108
|
+
parser (>= 3.0.1.1)
|
109
|
+
ruby-progressbar (1.11.0)
|
110
|
+
ruby_parser (3.18.1)
|
111
|
+
sexp_processor (~> 4.16)
|
112
|
+
rubycritic (4.6.1)
|
113
|
+
flay (~> 2.8)
|
114
|
+
flog (~> 4.4)
|
115
|
+
launchy (>= 2.0.0)
|
116
|
+
parser (>= 2.6.0)
|
117
|
+
rainbow (~> 3.0)
|
118
|
+
reek (~> 6.0, < 7.0)
|
119
|
+
ruby_parser (~> 3.8)
|
120
|
+
simplecov (>= 0.17.0)
|
121
|
+
tty-which (~> 0.4.0)
|
122
|
+
virtus (~> 1.0)
|
123
|
+
russian (0.6.0)
|
124
|
+
i18n (>= 0.5.0)
|
125
|
+
set (1.0.2)
|
126
|
+
sexp_processor (4.16.0)
|
127
|
+
simplecov (0.21.2)
|
128
|
+
docile (~> 1.1)
|
129
|
+
simplecov-html (~> 0.11)
|
130
|
+
simplecov_json_formatter (~> 0.1)
|
131
|
+
simplecov-console (0.9.1)
|
132
|
+
ansi
|
133
|
+
simplecov
|
134
|
+
terminal-table
|
135
|
+
simplecov-html (0.12.3)
|
136
|
+
simplecov_json_formatter (0.1.3)
|
137
|
+
sorted_set (1.0.3)
|
138
|
+
rbtree
|
139
|
+
set (~> 1.0)
|
140
|
+
sqlite3 (1.4.2)
|
141
|
+
terminal-table (3.0.2)
|
142
|
+
unicode-display_width (>= 1.1.1, < 3)
|
143
|
+
thread_safe (0.3.6)
|
144
|
+
timeouter (0.1.3.38794)
|
145
|
+
tty-which (0.4.2)
|
146
|
+
tzinfo (2.0.4)
|
147
|
+
concurrent-ruby (~> 1.0)
|
148
|
+
unicode-display_width (2.1.0)
|
149
|
+
virtus (1.0.5)
|
150
|
+
axiom-types (~> 0.1)
|
151
|
+
coercible (~> 1.0)
|
152
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
153
|
+
equalizer (~> 0.0, >= 0.0.9)
|
154
|
+
zeitwerk (2.5.3)
|
155
|
+
|
156
|
+
PLATFORMS
|
157
|
+
ruby
|
158
|
+
|
159
|
+
DEPENDENCIES
|
160
|
+
activerecord (~> 6.0)
|
161
|
+
bbk-app!
|
162
|
+
bundler (~> 2.0)
|
163
|
+
bunny-mock (~> 1.7.0)
|
164
|
+
byebug
|
165
|
+
database_cleaner (~> 1.7)
|
166
|
+
faker (~> 2)
|
167
|
+
rake (~> 12.0)
|
168
|
+
rspec (~> 3.0)
|
169
|
+
rspec_junit_formatter
|
170
|
+
rubocop
|
171
|
+
rubycritic
|
172
|
+
simplecov
|
173
|
+
simplecov-console
|
174
|
+
sqlite3 (~> 1.4)
|
175
|
+
|
176
|
+
BUNDLED WITH
|
177
|
+
2.2.33
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# BBK::App
|
2
|
+
|
3
|
+
Classes for building services based on BBK stack.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Adding to a gem:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# my-cool-gem.gemspec
|
11
|
+
|
12
|
+
Gem::Specification.new do |spec|
|
13
|
+
# ...
|
14
|
+
spec.add_dependency "bbk-app", "~> 1.0.0"
|
15
|
+
# ...
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
Or adding to your project:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# Gemfile
|
23
|
+
|
24
|
+
gem "bbk-app", "~> 1.0.0"
|
25
|
+
```
|
26
|
+
|
27
|
+
### Supported Ruby versions
|
28
|
+
|
29
|
+
* Ruby (MRI) >= 2.5.0
|
30
|
+
|
31
|
+
### Tested Ruby versions
|
32
|
+
|
33
|
+
* Ruby (MRI) 2.5.x
|
34
|
+
* Ruby (MRI) 3.0.x
|
35
|
+
|
36
|
+
## License
|
37
|
+
|
38
|
+
The gem is available as open source under the terms of the MIT License.
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'bbk/app'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
15
|
+
|
data/bin/setup
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module BBK
|
2
|
+
module App
|
3
|
+
class Dispatcher
|
4
|
+
class Message
|
5
|
+
|
6
|
+
attr_reader :consumer, :delivery_info, :headers, :payload, :body
|
7
|
+
|
8
|
+
def initialize(consumer, delivery_info, headers, body, *_args, **_kwargs)
|
9
|
+
@consumer = consumer
|
10
|
+
@delivery_info = delivery_info
|
11
|
+
@headers = headers.to_h.with_indifferent_access
|
12
|
+
@body = body
|
13
|
+
@payload = begin
|
14
|
+
JSON(body).with_indifferent_access
|
15
|
+
rescue StandardError
|
16
|
+
{}.with_indifferent_access
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def ack(*args, answer: nil, **kwargs)
|
21
|
+
consumer.ack(self, *args, answer: answer, **kwargs)
|
22
|
+
end
|
23
|
+
|
24
|
+
def nack(*args, error: nil, **kwargs)
|
25
|
+
consumer.nack(self, *args, error: error, **kwargs)
|
26
|
+
end
|
27
|
+
|
28
|
+
def message_id
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def reply_message_id(addon)
|
33
|
+
Digest::SHA1.hexdigest("#{addon}#{message_id}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
{
|
38
|
+
headers: headers,
|
39
|
+
body: body
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BBK
|
4
|
+
module App
|
5
|
+
class Dispatcher
|
6
|
+
class MessageStream
|
7
|
+
|
8
|
+
CLOSE_VALUE = :close
|
9
|
+
attr_reader :queue, :stream
|
10
|
+
|
11
|
+
def initialize(size: 10)
|
12
|
+
@queue = SizedQueue.new(size)
|
13
|
+
@closed = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def push(message)
|
17
|
+
@queue.push(message) unless @closed
|
18
|
+
end
|
19
|
+
alias << push
|
20
|
+
|
21
|
+
def each
|
22
|
+
return to_enum unless block_given?
|
23
|
+
return nil if @closed
|
24
|
+
|
25
|
+
loop do
|
26
|
+
value = @queue.pop
|
27
|
+
break if value == CLOSE_VALUE
|
28
|
+
|
29
|
+
yield(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def close
|
34
|
+
@closed = true
|
35
|
+
@queue << CLOSE_VALUE
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'bbk/app/dispatcher/message_stream'
|
2
|
+
|
3
|
+
module BBK
|
4
|
+
module App
|
5
|
+
class Dispatcher
|
6
|
+
class QueueStreamStrategy
|
7
|
+
|
8
|
+
def initialize(pool, logger:)
|
9
|
+
@pool = pool
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(consumers, &block)
|
14
|
+
@unblocker = Queue.new
|
15
|
+
@stream = BBK::App::Dispatcher::MessageStream.new(size: 10)
|
16
|
+
|
17
|
+
consumers.each {|cons| cons.run(@stream) }
|
18
|
+
@stream.each do |msg|
|
19
|
+
@logger.debug "[#{self.class}] Consumed message #{msg.headers}"
|
20
|
+
@pool.post(msg) do |m|
|
21
|
+
block.call(m)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
@pool.shutdown
|
27
|
+
rescue StandardError
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
@pool.kill unless @pool.wait_for_termination(@stop_queue_timeout)
|
31
|
+
ensure
|
32
|
+
@unblocker.push(:ok)
|
33
|
+
end
|
34
|
+
|
35
|
+
def push(*args)
|
36
|
+
@stream.push(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def stop(timeout = 5)
|
40
|
+
@stop_queue_timeout = timeout
|
41
|
+
|
42
|
+
begin
|
43
|
+
@stream.close
|
44
|
+
rescue StandardError
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
@unblocker.pop
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module BBK
|
2
|
+
module App
|
3
|
+
class Dispatcher
|
4
|
+
class Result
|
5
|
+
|
6
|
+
attr_accessor :route, :message
|
7
|
+
|
8
|
+
def initialize(route, message)
|
9
|
+
@route = route.is_a?(String) ? Dispatcher::Route.new(route) : route
|
10
|
+
|
11
|
+
raise 'route must be of type Dispatcher::Route' unless @route.is_a?(Dispatcher::Route)
|
12
|
+
|
13
|
+
@message = message
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BBK
|
4
|
+
module App
|
5
|
+
class Dispatcher
|
6
|
+
class Route
|
7
|
+
|
8
|
+
attr_reader :uri, :scheme, :domain, :routing_key
|
9
|
+
|
10
|
+
# Example: mq://gw@service.smev.request
|
11
|
+
def initialize(string)
|
12
|
+
@uri = URI(string)
|
13
|
+
@scheme = uri.scheme
|
14
|
+
@domain = uri.user
|
15
|
+
@routing_key = "#{uri.host}#{uri.path}"
|
16
|
+
|
17
|
+
# raise 'domain must present in route' if @domain.blank?
|
18
|
+
raise 'routing_key must present in route' if @routing_key.blank?
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@uri.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
if other.is_a?(String)
|
27
|
+
to_s == other
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require 'bbk/app/thread_pool'
|
3
|
+
require 'bbk/app/dispatcher/message_stream'
|
4
|
+
require 'bbk/app/dispatcher/message'
|
5
|
+
require 'bbk/app/dispatcher/queue_stream_strategy'
|
6
|
+
require 'bbk/app/dispatcher/result'
|
7
|
+
require 'bbk/app/dispatcher/route'
|
8
|
+
require 'bbk/utils/proxy_logger'
|
9
|
+
|
10
|
+
module BBK
|
11
|
+
module App
|
12
|
+
|
13
|
+
class SimplePoolFactory
|
14
|
+
|
15
|
+
def self.call(pool_size, queue_size)
|
16
|
+
BBK::App::ThreadPool.new(pool_size, queue: queue_size)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class ConcurrentPoolFactory
|
22
|
+
|
23
|
+
def self.call(pool_size, queue_size)
|
24
|
+
Concurrent::FixedThreadPool.new(pool_size, max_queue: queue_size,
|
25
|
+
fallback_policy: :caller_runs)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class Dispatcher
|
31
|
+
|
32
|
+
attr_accessor :force_quit
|
33
|
+
attr_reader :consumers, :publishers, :observer, :middlewares, :logger
|
34
|
+
|
35
|
+
ANSWER_DOMAIN = 'answer'
|
36
|
+
|
37
|
+
def initialize(observer, pool_size: 3, logger: BBK::App.logger, pool_factory: SimplePoolFactory, stream_strategy: QueueStreamStrategy)
|
38
|
+
@observer = observer
|
39
|
+
@pool_size = pool_size
|
40
|
+
logger = logger.respond_to?(:tagged) ? logger : ActiveSupport::TaggedLogging.new(logger)
|
41
|
+
@logger = BBK::Utils::ProxyLogger.new(logger, tags: 'Dispatcher')
|
42
|
+
@consumers = []
|
43
|
+
@publishers = []
|
44
|
+
@middlewares = []
|
45
|
+
@pool_factory = pool_factory
|
46
|
+
@stream_strategy_class = stream_strategy
|
47
|
+
@force_quit = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def register_consumer(consumer)
|
51
|
+
consumers << consumer
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_publisher(publisher)
|
55
|
+
publishers << publisher
|
56
|
+
end
|
57
|
+
|
58
|
+
def register_middleware(middleware)
|
59
|
+
middlewares << middleware
|
60
|
+
end
|
61
|
+
|
62
|
+
# Run all consumers and blocks on message processing
|
63
|
+
def run
|
64
|
+
@pool = @pool_factory.call(@pool_size, 10)
|
65
|
+
@stream_strategy = @stream_strategy_class.new(@pool, logger: logger)
|
66
|
+
ActiveSupport::Notifications.instrument 'dispatcher.run', dispatcher: self
|
67
|
+
|
68
|
+
@stream_strategy.run(consumers) do |msg|
|
69
|
+
begin
|
70
|
+
logger.tagged(msg.headers[:message_id]) do
|
71
|
+
process msg
|
72
|
+
end
|
73
|
+
rescue StandardError => e
|
74
|
+
logger.fatal "E[#{@stream_strategy_class}]: #{e}"
|
75
|
+
logger.fatal "E[#{@stream_strategy_class}]: #{e.backtrace.join("\n")}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# stop dispatcher and wait for termination
|
81
|
+
# Чтоб остановить диспетчер надо:
|
82
|
+
# 1. остановить консьюмеры
|
83
|
+
# 2. остановить прием новых сообщений - @stream.close
|
84
|
+
# 3. дождаться обработки всего в очереди или таймаут
|
85
|
+
# 4. остановить потоки
|
86
|
+
# 5. остановить паблишеры
|
87
|
+
def close(_timeout = 5)
|
88
|
+
ActiveSupport::Notifications.instrument 'dispatcher.close', dispatcher: self
|
89
|
+
consumers.each do |cons|
|
90
|
+
begin
|
91
|
+
cons.stop
|
92
|
+
rescue StandardError => e
|
93
|
+
logger.error "Consumer #{cons} stop error: #{e}"
|
94
|
+
logger.debug e.backtrace
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@stream_strategy.stop(5)
|
99
|
+
|
100
|
+
consumers.each do |cons|
|
101
|
+
begin
|
102
|
+
cons.close
|
103
|
+
rescue StandardError => e
|
104
|
+
logger.error "Consumer #{cons} close error: #{e}"
|
105
|
+
logger.debug e.backtrace
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
publishers.each do |pub|
|
110
|
+
begin
|
111
|
+
pub.close
|
112
|
+
rescue StandardError => e
|
113
|
+
logger.error "Publisher #{pub} close error: #{e}"
|
114
|
+
logger.debug e.backtrace
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def process(message)
|
122
|
+
results = build_processing_stack.call(message).select do |e|
|
123
|
+
e.is_a? BBK::App::Dispatcher::Result
|
124
|
+
end
|
125
|
+
logger.debug "There are #{results.count} results to send from #{message.headers[:message_id]}..."
|
126
|
+
send_results(message, results).value
|
127
|
+
rescue StandardError => e
|
128
|
+
ActiveSupport::Notifications.instrument 'dispatcher.exception', msg: message, exception: e
|
129
|
+
message.nack(error: e)
|
130
|
+
close if force_quit
|
131
|
+
end
|
132
|
+
|
133
|
+
def process_message(message)
|
134
|
+
matched, processor = find_processor(message)
|
135
|
+
results = []
|
136
|
+
begin
|
137
|
+
is_unknown = @observer.instance_variable_get('@default') == processor
|
138
|
+
ActiveSupport::Notifications.instrument 'dispatcher.request.process', msg: message, match: matched, unknown: is_unknown do
|
139
|
+
processor.call(message, results: results)
|
140
|
+
end
|
141
|
+
rescue StandardError => e
|
142
|
+
if processor.respond_to?(:on_error)
|
143
|
+
results = processor.on_error(message, e)
|
144
|
+
else
|
145
|
+
raise
|
146
|
+
end
|
147
|
+
end
|
148
|
+
[results].flatten
|
149
|
+
rescue StandardError => e
|
150
|
+
ActiveSupport::Notifications.instrument 'dispatcher.request.exception', msg: message, match: matched, processor: processor, exception: e
|
151
|
+
raise
|
152
|
+
end
|
153
|
+
|
154
|
+
def find_processor(msg)
|
155
|
+
matched, callback = @observer.match(msg.headers, msg.payload, msg.delivery_info)
|
156
|
+
[matched, callback.is_a?(BBK::App::Factory) ? callback.create : callback]
|
157
|
+
end
|
158
|
+
|
159
|
+
def build_processing_stack
|
160
|
+
stack = proc{|msg| process_message(msg) }
|
161
|
+
middlewares.reduce(stack) do |stack, middleware|
|
162
|
+
if middleware.respond_to?(:build)
|
163
|
+
middleware.build(stack)
|
164
|
+
else
|
165
|
+
middleware.new(stack)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def send_results(incoming, results)
|
171
|
+
message_id = incoming.headers[:message_id]
|
172
|
+
|
173
|
+
answer = results.find {|msg| msg.route.domain == ANSWER_DOMAIN }
|
174
|
+
Concurrent::Promises.zip_futures(*results.map do |result|
|
175
|
+
publish_result(result)
|
176
|
+
end).then do |_successes|
|
177
|
+
incoming.ack(answer: answer)
|
178
|
+
end.rescue do |*errors|
|
179
|
+
error = errors.compact.first
|
180
|
+
ActiveSupport::Notifications.instrument 'dispatcher.request.result_rejected',
|
181
|
+
msg: incoming, message: error.inspect
|
182
|
+
logger.error "[Message#{message_id}] Publish failed: #{error.inspect}"
|
183
|
+
incoming.nack(error: error)
|
184
|
+
close if force_quit
|
185
|
+
rescue StandardError => e
|
186
|
+
warn e.backtrace
|
187
|
+
warn "[CRITICAL] #{self.class} [#{Process.pid}] failure exiting: #{e.inspect}"
|
188
|
+
ActiveSupport::Notifications.instrument 'dispatcher.exception', msg: incoming,
|
189
|
+
exception: e
|
190
|
+
sleep(10)
|
191
|
+
exit!(1)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# @return [Concurrent::Promises::ResolvableFuture]
|
196
|
+
def publish_result(result)
|
197
|
+
route = result.route
|
198
|
+
logger.debug "Publish result to #{route} ..."
|
199
|
+
publisher = publishers.find {|pub| pub.protocols.include?(route.scheme) }
|
200
|
+
raise "Not found publisher for scheme #{route.scheme}" if publisher.nil?
|
201
|
+
|
202
|
+
# return Concurrent::Promises.resolvable_future
|
203
|
+
publisher.publish(result)
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BBK
|
4
|
+
module App
|
5
|
+
class Factory
|
6
|
+
|
7
|
+
attr_accessor :klass, :instanceargs, :instancekwargs
|
8
|
+
|
9
|
+
def initialize(klass, *args, **kwargs)
|
10
|
+
@klass = klass
|
11
|
+
@instanceargs = args
|
12
|
+
@instancekwargs = kwargs
|
13
|
+
end
|
14
|
+
|
15
|
+
def create
|
16
|
+
klass.new(*instanceargs, **instancekwargs)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(*args, **kwargs)
|
20
|
+
create.call(*args, **kwargs)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|