bbk-app 1.0.0.72899
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 +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
|
+
|