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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3844f5f141564257d45c59386c82683d9adc62ec
4
- data.tar.gz: d5fec104b21c27ebd65b439a66ac702617324d93
3
+ metadata.gz: 0a342aa3de500e0ece6c60e76e57122a869c5e74
4
+ data.tar.gz: 5256c6fcebf654fc57e86189b6c1023b54d6e742
5
5
  SHA512:
6
- metadata.gz: bd36645f66fea70b5347acbb86673eee29ac8a3e89e02ad43dcc0c9d5d585efbe38f5d3ebb175ae5f4075aa94779387c4e17dfc8839f0e6029642aa8a95b10c7
7
- data.tar.gz: e107b55e67021305429092c26c69d62f5c7af55f011763ab5dbf7950d80bb627d8d826fe2303218e5c543879f81aefc1ce75eac313f7f46b701f006a6104aef2
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 = 0
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 request.action == "incr"
28
- response.body = "#{request.params[:num].to_i + 1}. Hello, World!"
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
- response.not_found
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
- puts "Total: #{count}"
54
- First.consumers.map(&:terminate)
72
+ #First.consumers.map(&:terminate)
data/fleck.gemspec CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency "bunny", "~> 2.2"
33
33
  spec.add_dependency "thread_safe", "~> 0.3"
34
34
  spec.add_dependency "oj", "~> 2.14"
35
+ spec.add_dependency "ztimer", "~> 0.3"
35
36
  end
@@ -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(exchange, routing_key, reply_to, headers = {}, params = {}, &callback)
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
- @exchange = exchange
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
- @exchange.publish(data, routing_key: @routing_key, reply_to: @reply_to, correlation_id: @id)
46
- @lock.synchronize { @condition.wait(@lock) } unless async
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 { @condition.signal }
51
- @ended_at = Time.now.to_f
52
- logger.debug "Done in #{((@ended_at - @started_at).round(5) * 1000).round(2)} ms"
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[metadata[:correlation_id]]
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
- @requests.delete metadata[:correlation_id]
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
- request = Fleck::Client::Request.new(@exchange, queue, @reply_queue.name, headers, params, &block)
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 => e
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
- @requests.each do |id, request|
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
- request.cancel!
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 = 200
13
- @errors = []
14
- @headers = {}
15
- @body = nil
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)
@@ -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
- @__subscription = @__queue.subscribe do |delivery_info, metadata, payload|
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
- logger.debug "Sending response: #{response}"
128
- @__exchange.publish(response.to_json, routing_key: metadata.reply_to, correlation_id: metadata.correlation_id)
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
@@ -1,3 +1,3 @@
1
1
  module Fleck
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/fleck.rb CHANGED
@@ -5,6 +5,7 @@ require "bunny"
5
5
  require "thread_safe"
6
6
  require "securerandom"
7
7
  require "oj"
8
+ require "ztimer"
8
9
  require "fleck/version"
9
10
  require "fleck/hash_with_indifferent_access"
10
11
  require "fleck/configuration"
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.2.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-02-18 00:00:00.000000000 Z
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