fleck 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|