fleck 0.2.0 → 0.3.0
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/CHANGELOG.md +17 -0
- data/example.rb +25 -7
- data/fleck.gemspec +1 -0
- data/lib/fleck/client/request.rb +25 -8
- data/lib/fleck/client/response.rb +1 -1
- data/lib/fleck/client.rb +44 -8
- data/lib/fleck/consumer/request.rb +1 -1
- data/lib/fleck/consumer/response.rb +19 -4
- data/lib/fleck/consumer.rb +49 -3
- data/lib/fleck/version.rb +1 -1
- data/lib/fleck.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a342aa3de500e0ece6c60e76e57122a869c5e74
|
4
|
+
data.tar.gz: 5256c6fcebf654fc57e86189b6c1023b54d6e742
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec260bbe626542c479ab2dc5e3fd78baa44b9cda4b521ae5cfe8f8a07e4c8090c1cae76081cf07e21af13249595e9ebd48dd39ad254a8f94db3ab700e13fb56c
|
7
|
+
data.tar.gz: 23b1e4c40ad25edaa644e920f288a0fb38784a07cf6d327dde7727394f10e6c5fe1eff773c8a89f52d80dfbf47d69cc337f4a9f5bca91dbe20c72463a5bf2139
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## (develop)
|
4
|
+
- **FIX** Use `:compat` mode when using `Oj` gem to dump/load JSON content.
|
5
|
+
- **FIX** Prevent unnecessary `Fleck::Request` lock for response reception if the response already received.
|
6
|
+
- **NEW** Implemented a timeout functionality for asynchronous request, so that if the request isn't completed within that timeout, it will be canceled and removed from
|
7
|
+
requests list.
|
8
|
+
- **NEW** Set `mandatory: true` when publishing the request to RabbitMQ for both `Fleck::Client` and `Fleck::Consumer`, in order to ensure that requests and responses
|
9
|
+
are enqueued for sure to RabbitMQ.
|
10
|
+
- **NEW** Implemented `#pause` and `#resume` methods for `Fleck::Consumer`, that allows to pause the consumer from consuming messages.
|
11
|
+
- **NEW** `Fleck::Consumer::Response#reject!` support, that allows to reject the processed message. By default `requeue` parameter is set to `false`, so that
|
12
|
+
failed requests aren't requeued. You should call `response.reject(requeue: true)` within the `on_message` method, if you want to requeue the processing
|
13
|
+
message.
|
14
|
+
|
15
|
+
## v0.2.0 (18 February 2016)
|
16
|
+
- **NEW** `timeout` (synchronous requests only) and `queue` support for `Fleck::Client#request`
|
17
|
+
- **NEW** Keywords arguments for `Fleck::Client#request` method (ex. `client.request(headers: {h1: v1, ...}, params: {p1: v2, ...}`)
|
data/example.rb
CHANGED
@@ -15,7 +15,10 @@ end
|
|
15
15
|
connection = Fleck.connection(host: "127.0.0.1", port: 5672, user: user, pass: pass, vhost: "/")
|
16
16
|
client = Fleck::Client.new(connection, "example.queue")
|
17
17
|
|
18
|
-
count
|
18
|
+
count = 0
|
19
|
+
success = 0
|
20
|
+
failure = 0
|
21
|
+
|
19
22
|
mutex = Mutex.new
|
20
23
|
lock = Mutex.new
|
21
24
|
condition = ConditionVariable.new
|
@@ -24,17 +27,22 @@ class First < Fleck::Consumer
|
|
24
27
|
configure queue: "example.queue", concurrency: CONCURRENCY.to_i
|
25
28
|
|
26
29
|
def on_message(request, response)
|
27
|
-
if
|
28
|
-
|
30
|
+
if rand > 0.1
|
31
|
+
if request.action == "incr"
|
32
|
+
response.body = "#{request.params[:num].to_i + 1}. Hello, World!"
|
33
|
+
else
|
34
|
+
response.not_found
|
35
|
+
end
|
29
36
|
else
|
30
|
-
|
37
|
+
logger.warn "REJECTING REQUEST {headers: #{request.headers}, params: #{request.params}"
|
38
|
+
response.reject!(requeue: true)
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
35
43
|
Thread.new do
|
36
44
|
SAMPLES.times do |i|
|
37
|
-
client.request({action: :incr}, {num: i}, true) do |request, response|
|
45
|
+
client.request(headers: {action: :incr}, params: {num: i}, async: true, timeout: 1) do |request, response|
|
38
46
|
if response.status == 200
|
39
47
|
request.logger.debug response.body
|
40
48
|
else
|
@@ -42,13 +50,23 @@ Thread.new do
|
|
42
50
|
end
|
43
51
|
mutex.synchronize do
|
44
52
|
count += 1
|
53
|
+
if response.status == 200
|
54
|
+
success += 1
|
55
|
+
else
|
56
|
+
failure += 1
|
57
|
+
end
|
58
|
+
|
45
59
|
lock.synchronize { condition.signal } if count >= SAMPLES
|
46
60
|
end
|
47
61
|
end
|
48
62
|
end
|
49
63
|
end
|
50
64
|
|
65
|
+
at_exit do
|
66
|
+
puts "Total: #{count}, Success: #{success}, Failure: #{failure}"
|
67
|
+
end
|
68
|
+
|
51
69
|
lock.synchronize { condition.wait(lock) }
|
70
|
+
exit
|
52
71
|
|
53
|
-
|
54
|
-
First.consumers.map(&:terminate)
|
72
|
+
#First.consumers.map(&:terminate)
|
data/fleck.gemspec
CHANGED
data/lib/fleck/client/request.rb
CHANGED
@@ -3,25 +3,28 @@ module Fleck
|
|
3
3
|
class Client::Request
|
4
4
|
include Fleck::Loggable
|
5
5
|
|
6
|
-
attr_reader :id, :response
|
6
|
+
attr_reader :id, :response, :completed
|
7
7
|
|
8
|
-
def initialize(
|
8
|
+
def initialize(client, routing_key, reply_to, headers: {}, params: {}, timeout: nil, &callback)
|
9
9
|
@id = SecureRandom.uuid
|
10
10
|
logger.progname += " #{@id}"
|
11
11
|
|
12
12
|
logger.debug "Preparing new request"
|
13
13
|
|
14
|
-
@
|
14
|
+
@client = client
|
15
15
|
@routing_key = routing_key
|
16
16
|
@reply_to = reply_to
|
17
17
|
@params = params
|
18
18
|
@headers = headers
|
19
|
+
@timeout = timeout
|
19
20
|
@response = nil
|
20
21
|
@lock = Mutex.new
|
21
22
|
@condition = ConditionVariable.new
|
22
23
|
@callback = callback
|
23
24
|
@started_at = nil
|
24
25
|
@ended_at = nil
|
26
|
+
@completed = false
|
27
|
+
@async = false
|
25
28
|
|
26
29
|
logger.debug "Request prepared"
|
27
30
|
end
|
@@ -36,20 +39,34 @@ module Fleck
|
|
36
39
|
|
37
40
|
def send!(async = false)
|
38
41
|
@started_at = Time.now.to_f
|
42
|
+
@async = async
|
39
43
|
data = Oj.dump({
|
40
44
|
headers: @headers,
|
41
45
|
params: @params
|
42
46
|
}, mode: :compat)
|
43
47
|
logger.debug("Sending request with data: #{data}")
|
44
48
|
|
45
|
-
|
46
|
-
|
49
|
+
options = { routing_key: @routing_key, reply_to: @reply_to, correlation_id: @id, mandatory: true }
|
50
|
+
options[:expiration] = (@timeout * 1000).to_i unless @timeout.nil?
|
51
|
+
|
52
|
+
@client.publish(data, options)
|
53
|
+
|
54
|
+
@lock.synchronize do
|
55
|
+
unless @async || @completed
|
56
|
+
logger.debug("Waiting for response")
|
57
|
+
@condition.wait(@lock)
|
58
|
+
logger.debug("Request terminated.")
|
59
|
+
end
|
60
|
+
end
|
47
61
|
end
|
48
62
|
|
49
63
|
def complete!
|
50
|
-
@lock.synchronize
|
51
|
-
|
52
|
-
|
64
|
+
@lock.synchronize do
|
65
|
+
@completed = true
|
66
|
+
@ended_at = Time.now.to_f
|
67
|
+
logger.debug "Done #{@async ? 'async' : 'synchronized'} in #{((@ended_at - @started_at).round(5) * 1000).round(2)} ms"
|
68
|
+
@condition.signal unless @async
|
69
|
+
end
|
53
70
|
end
|
54
71
|
|
55
72
|
def cancel!
|
@@ -5,7 +5,7 @@ module Fleck
|
|
5
5
|
|
6
6
|
attr_accessor :status, :headers, :body, :errors
|
7
7
|
def initialize(payload)
|
8
|
-
@data = Oj.load(payload).to_hash_with_indifferent_access
|
8
|
+
@data = Oj.load(payload, mode: :compat).to_hash_with_indifferent_access
|
9
9
|
@status = @data["status"]
|
10
10
|
@headers = @data["headers"] || {}
|
11
11
|
@body = @data["body"]
|
data/lib/fleck/client.rb
CHANGED
@@ -10,15 +10,31 @@ module Fleck
|
|
10
10
|
@exchange = @channel.default_exchange
|
11
11
|
@reply_queue = @channel.queue("", exclusive: true)
|
12
12
|
@requests = ThreadSafe::Hash.new
|
13
|
+
@terminated = false
|
14
|
+
@mutex = Mutex.new
|
15
|
+
|
16
|
+
@exchange.on_return do |return_info, metadata, content|
|
17
|
+
begin
|
18
|
+
logger.warn "Request #{metadata[:correlation_id]} returned"
|
19
|
+
request = @requests[metadata[:correlation_id]]
|
20
|
+
if request
|
21
|
+
request.cancel!
|
22
|
+
@requests.delete metadata[:correlation_id]
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
logger.error e.inspect + "\n" + e.backtrace.join("\n")
|
26
|
+
end
|
27
|
+
end
|
13
28
|
|
14
29
|
@subscription = @reply_queue.subscribe do |delivery_info, metadata, payload|
|
15
30
|
begin
|
16
31
|
logger.debug "Response received: #{payload}"
|
17
|
-
request = @requests
|
32
|
+
request = @requests.delete metadata[:correlation_id]
|
18
33
|
if request
|
19
34
|
request.response = Fleck::Client::Response.new(payload)
|
20
35
|
request.complete!
|
21
|
-
|
36
|
+
else
|
37
|
+
logger.warn "Request #{metadata[:correlation_id]} not found!"
|
22
38
|
end
|
23
39
|
rescue => e
|
24
40
|
logger.error e.inspect + "\n" + e.backtrace.join("\n")
|
@@ -33,18 +49,31 @@ module Fleck
|
|
33
49
|
end
|
34
50
|
|
35
51
|
def request(headers: {}, params: {}, async: false, timeout: nil, queue: @queue_name, &block)
|
36
|
-
|
52
|
+
if @terminated
|
53
|
+
return Fleck::Client::Response.new(Oj.dump({status: 503, errors: ['Service Unavailable'], body: nil} , mode: :compat))
|
54
|
+
end
|
55
|
+
|
56
|
+
request = Fleck::Client::Request.new(self, queue, @reply_queue.name, headers: headers, params: params, timeout: timeout, &block)
|
37
57
|
@requests[request.id] = request
|
38
58
|
if timeout && !async
|
39
59
|
begin
|
40
60
|
Timeout.timeout(timeout.to_f) do
|
41
61
|
request.send!(false)
|
42
62
|
end
|
43
|
-
rescue Timeout::Error
|
63
|
+
rescue Timeout::Error
|
44
64
|
logger.warn "Failed to get any response in #{timeout} seconds for request #{request.id.to_s.color(:red)}! The request will be canceled."
|
45
65
|
request.cancel!
|
46
66
|
@requests.delete request.id
|
47
67
|
end
|
68
|
+
elsif timeout && async
|
69
|
+
request.send!(async)
|
70
|
+
Ztimer.after(timeout * 1000) do |slot|
|
71
|
+
unless request.completed
|
72
|
+
logger.warn "TIMEOUT #{request.id} (#{((slot.executed_at - slot.enqueued_at) / 1000.to_f).round(2)} ms)"
|
73
|
+
request.cancel!
|
74
|
+
@requests.delete request.id
|
75
|
+
end
|
76
|
+
end
|
48
77
|
else
|
49
78
|
request.send!(async)
|
50
79
|
end
|
@@ -52,17 +81,24 @@ module Fleck
|
|
52
81
|
return request.response
|
53
82
|
end
|
54
83
|
|
84
|
+
def publish(data, options)
|
85
|
+
return if @terminated
|
86
|
+
@mutex.synchronize { @exchange.publish(data, options) }
|
87
|
+
end
|
88
|
+
|
55
89
|
def terminate
|
90
|
+
@terminated = true
|
56
91
|
logger.info "Unsubscribing from #{@reply_queue.name}"
|
57
|
-
@
|
92
|
+
@subscription.cancel # stop receiving new messages
|
93
|
+
logger.info "Canceling pending requests"
|
94
|
+
# cancel pending requests
|
95
|
+
while item = @requests.shift do
|
58
96
|
begin
|
59
|
-
|
97
|
+
item[1].cancel!
|
60
98
|
rescue => e
|
61
99
|
logger.error e.inspect + "\n" + e.backtrace.join("\n")
|
62
100
|
end
|
63
101
|
end
|
64
|
-
@requests.clear
|
65
|
-
@subscription.cancel
|
66
102
|
end
|
67
103
|
end
|
68
104
|
end
|
@@ -25,7 +25,7 @@ module Fleck
|
|
25
25
|
|
26
26
|
def parse_request!
|
27
27
|
logger.debug "Parsing request: #{@payload}"
|
28
|
-
@data = Oj.load(@payload).to_hash_with_indifferent_access
|
28
|
+
@data = Oj.load(@payload, mode: :compat).to_hash_with_indifferent_access
|
29
29
|
@headers = @data["headers"] || {}
|
30
30
|
@action = @headers["action"]
|
31
31
|
@params = @data["params"] || {}
|
@@ -9,10 +9,25 @@ module Fleck
|
|
9
9
|
@id = request_id
|
10
10
|
logger.progname += " #{@id}"
|
11
11
|
|
12
|
-
@status
|
13
|
-
@errors
|
14
|
-
@headers
|
15
|
-
@body
|
12
|
+
@status = 200
|
13
|
+
@errors = []
|
14
|
+
@headers = {}
|
15
|
+
@body = nil
|
16
|
+
@rejected = false
|
17
|
+
@requeue = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def reject!(requeue: false)
|
21
|
+
@rejected = true
|
22
|
+
@requeue = requeue
|
23
|
+
end
|
24
|
+
|
25
|
+
def rejected?
|
26
|
+
return @rejected
|
27
|
+
end
|
28
|
+
|
29
|
+
def requeue?
|
30
|
+
return @requeue
|
16
31
|
end
|
17
32
|
|
18
33
|
def not_found(msg = nil)
|
data/lib/fleck/consumer.rb
CHANGED
@@ -45,6 +45,7 @@ module Fleck
|
|
45
45
|
def initialize(thread_id = nil)
|
46
46
|
@__thread_id = thread_id
|
47
47
|
@__connection = nil
|
48
|
+
@__consumer_tag = nil
|
48
49
|
|
49
50
|
@__host = configs[:host]
|
50
51
|
@__port = configs[:port]
|
@@ -69,6 +70,7 @@ module Fleck
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def terminate
|
73
|
+
pause
|
72
74
|
unless @__channel.closed?
|
73
75
|
@__channel.close
|
74
76
|
logger.info "Consumer successfully terminated."
|
@@ -87,6 +89,35 @@ module Fleck
|
|
87
89
|
@configs ||= self.class.configs
|
88
90
|
end
|
89
91
|
|
92
|
+
def connection
|
93
|
+
return @__connection
|
94
|
+
end
|
95
|
+
|
96
|
+
def channel
|
97
|
+
return @__channel
|
98
|
+
end
|
99
|
+
|
100
|
+
def queue
|
101
|
+
return @__queue
|
102
|
+
end
|
103
|
+
|
104
|
+
def exchange
|
105
|
+
return @__exchange
|
106
|
+
end
|
107
|
+
|
108
|
+
def subscription
|
109
|
+
return @__subscription
|
110
|
+
end
|
111
|
+
|
112
|
+
def pause
|
113
|
+
cancel_ok = @__subscription.cancel
|
114
|
+
@__consumer_tag = cancel_ok.consumer_tag
|
115
|
+
end
|
116
|
+
|
117
|
+
def resume
|
118
|
+
subscribe!
|
119
|
+
end
|
120
|
+
|
90
121
|
protected
|
91
122
|
|
92
123
|
def connect!
|
@@ -108,7 +139,11 @@ module Fleck
|
|
108
139
|
|
109
140
|
def subscribe!
|
110
141
|
logger.debug "Consuming from queue: #{@__queue_name.color(:green)}"
|
111
|
-
|
142
|
+
|
143
|
+
options = { manual_ack: true }
|
144
|
+
options[:consumer_tag] = @__consumer_tag if @__consumer_tag
|
145
|
+
|
146
|
+
@__subscription = @__queue.subscribe(options) do |delivery_info, metadata, payload|
|
112
147
|
response = Fleck::Consumer::Response.new(metadata.correlation_id)
|
113
148
|
begin
|
114
149
|
request = Fleck::Consumer::Request.new(metadata, payload)
|
@@ -124,8 +159,19 @@ module Fleck
|
|
124
159
|
response.errors << 'Internal Server Error'
|
125
160
|
end
|
126
161
|
|
127
|
-
|
128
|
-
|
162
|
+
if response.rejected?
|
163
|
+
# the request was rejected, so we have to notify the reject
|
164
|
+
logger.warn "Request #{response.id} was rejected!"
|
165
|
+
@__channel.reject(delivery_info.delivery_tag, response.requeue?)
|
166
|
+
else
|
167
|
+
logger.debug "Sending response: #{response}"
|
168
|
+
if @__channel.closed?
|
169
|
+
logger.warn "Channel already closed! The response #{metadata.correlation_id} is going to be dropped."
|
170
|
+
else
|
171
|
+
@__exchange.publish(response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id, mandatory: true)
|
172
|
+
@__channel.ack(delivery_info.delivery_tag)
|
173
|
+
end
|
174
|
+
end
|
129
175
|
end
|
130
176
|
end
|
131
177
|
|
data/lib/fleck/version.rb
CHANGED
data/lib/fleck.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fleck
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Groza Sergiu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.14'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ztimer
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.3'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.3'
|
111
125
|
description: "\n Fleck is a library for syncronous and asyncronous communication
|
112
126
|
over Message Queues services. Unlike a common\n HTTP communication, Fleck requests
|
113
127
|
and responses are pure JSON messages.\n "
|
@@ -120,6 +134,7 @@ files:
|
|
120
134
|
- ".gitignore"
|
121
135
|
- ".rspec"
|
122
136
|
- ".travis.yml"
|
137
|
+
- CHANGELOG.md
|
123
138
|
- CODE_OF_CONDUCT.md
|
124
139
|
- Gemfile
|
125
140
|
- LICENSE.txt
|