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 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