klomp 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Changes
2
2
  --------------------------------------------------------------------------------
3
3
 
4
+ 0.0.4 (2012/6/22)
5
+ ================================================================================
6
+
7
+ - Add fibonacci-based retry/reconnect back-off logic
8
+ - Add generated UUID message IDs to every message (and log them)
9
+
4
10
  0.0.3 (2012/6/21)
5
11
  ================================================================================
6
12
 
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "onstomp"
3
+ gem "onstomp", "~> 1.0.7"
4
4
  # gem 'onstomp', :path => '../onstomp'
5
5
  gem "json"
6
+ gem "uuid", "~> 2.3.5"
6
7
 
7
8
  group :development do
8
9
  gem "foreman"
data/Gemfile.lock CHANGED
@@ -5,9 +5,14 @@ GEM
5
5
  foreman (0.46.0)
6
6
  thor (>= 0.13.6)
7
7
  json (1.7.1)
8
+ macaddr (1.6.1)
9
+ systemu (~> 2.5.0)
8
10
  onstomp (1.0.7)
9
11
  rake (0.9.2.2)
12
+ systemu (2.5.1)
10
13
  thor (0.15.2)
14
+ uuid (2.3.5)
15
+ macaddr (~> 1.0)
11
16
 
12
17
  PLATFORMS
13
18
  ruby
@@ -16,5 +21,6 @@ DEPENDENCIES
16
21
  ZenTest
17
22
  foreman
18
23
  json
19
- onstomp
24
+ onstomp (~> 1.0.7)
20
25
  rake
26
+ uuid (~> 2.3.5)
data/README.md CHANGED
@@ -36,6 +36,21 @@ manages connections. For example, while the `connected?` method normally
36
36
  returns a boolean value, Klomp's `connected?` will return an array of booleans
37
37
  (i.e. one result for each broker).
38
38
 
39
+ ### Fibonacci back-off retry behavior
40
+
41
+ The OnStomp failover client takes `:retry_attempts` and `:retry_delay` options,
42
+ and Klomp supports these too. However, if you do not specify either of these
43
+ values, Klomp's default behavior will be to try to reconnect indefinitely, but
44
+ use a fibonacci backoff approach, i.e., it will wait `fib(N)` seconds before
45
+ trying to reconnect on the Nth attempt.
46
+
47
+ ### Message IDs
48
+
49
+ Klomp uses the `uuid` gem to generate per-message unique identifiers. To disable
50
+ generated message IDs, pass `:uuid => false` in the options hash. To customize
51
+ to use your own generator, simply pass `:uuid => object` where the object you
52
+ pass implements a `#generate` method that returns a string ID.
53
+
39
54
  ### Additional options for Klomp::Client
40
55
 
41
56
  <table>
@@ -59,6 +74,11 @@ returns a boolean value, Klomp's `connected?` will return an array of booleans
59
74
  <td>false</td>
60
75
  <td>Logger object</td>
61
76
  </tr>
77
+ <tr>
78
+ <td>:uuid</td>
79
+ <td>UUID.new</td>
80
+ <td>UUID generator object, responds to :generate and returns an ID</td>
81
+ </tr>
62
82
  </table>
63
83
 
64
84
  ## Developers
data/klomp.gemspec CHANGED
@@ -17,4 +17,5 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_dependency("onstomp", "~> 1.0.7")
19
19
  gem.add_dependency("json")
20
+ gem.add_dependency("uuid", "~> 2.3.5")
20
21
  end
data/lib/klomp.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'klomp/client'
2
2
 
3
3
  module Klomp
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.4'
5
5
  end
data/lib/klomp/client.rb CHANGED
@@ -1,8 +1,14 @@
1
1
  require 'onstomp'
2
2
  require 'onstomp/failover'
3
3
  require 'json'
4
+ require 'uuid'
4
5
  require 'logger'
5
6
 
7
+ class OnStomp::Failover::Client
8
+ # Save previous N, N-1 delays for fibonacci backoff
9
+ attr_accessor :prev_retry_delay
10
+ end
11
+
6
12
  module Klomp
7
13
 
8
14
  class Client
@@ -13,10 +19,13 @@ module Klomp
13
19
  @translate_json = options.fetch(:translate_json, true)
14
20
  @auto_reply_to = options.fetch(:auto_reply_to, true)
15
21
  @logger = options.fetch(:logger, nil)
22
+ @uuid = options.fetch(:uuid) { UUID.new }
23
+
24
+ @fib_retry_backoff = !options.has_key?(:retry_attempts) && !options.has_key?(:retry_delay)
16
25
 
17
26
  # defaults for retry delay and attempts
18
- options[:retry_delay] ||= 2
19
- options[:retry_attempts] ||= 10
27
+ options[:retry_delay] ||= 1
28
+ options[:retry_attempts] ||= -1
20
29
 
21
30
  if uri.is_a?(Array)
22
31
  @write_conn = OnStomp::Failover::Client.new(uri, options)
@@ -46,16 +55,16 @@ module Klomp
46
55
  self
47
56
  end
48
57
 
49
- def send(*args, &block)
50
- if @translate_json && args[1].respond_to?(:to_json)
51
- args[1] = args[1].to_json
52
- args[2] ||= {}
53
- args[2][:'content-type'] = 'application/json'
58
+ def send(dest, body, headers={}, &cb)
59
+ if @translate_json && body.respond_to?(:to_json)
60
+ body = body.to_json
61
+ headers[:'content-type'] = 'application/json'
54
62
  else
55
- args[1] = args[1].to_s
63
+ body = body.to_s
56
64
  end
57
- log.info("[Sending] Destination=#{args[0]} Body=#{args[1]} Headers=#{args[2]}") if log
58
- @write_conn.send(*args, &block)
65
+ uuid = headers[:id] = @uuid.generate if @uuid
66
+ log.info("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
67
+ @write_conn.send(dest, body, headers, &cb)
59
68
  end
60
69
  alias publish send
61
70
 
@@ -63,7 +72,7 @@ module Klomp
63
72
  frames = []
64
73
  @read_conn.each do |c|
65
74
  frames << c.subscribe(*args) do |msg|
66
- log.info("[Received] Body=#{msg.body} Headers=#{msg.headers.to_hash.sort}") if log
75
+ log.info("[Received] ID=#{msg[:id]} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
67
76
  if @translate_json
68
77
  msg.body = begin
69
78
  JSON.parse(msg.body)
@@ -126,6 +135,16 @@ module Klomp
126
135
  def configure_connections
127
136
  klomp_client = self
128
137
  @all_conn.each do |c|
138
+ if @fib_retry_backoff
139
+ c.before_failover_retry do |conn, attempt|
140
+ if attempt == 1
141
+ conn.prev_retry_delay, conn.retry_delay = 0, 1
142
+ else
143
+ conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
144
+ end
145
+ end
146
+ end
147
+
129
148
  c.on_failover_connect_failure do
130
149
  klomp_client.last_connect_exception = $!
131
150
  end
data/test/test_client.rb CHANGED
@@ -52,15 +52,14 @@ describe Klomp::Client do
52
52
 
53
53
  it 'has a logger' do
54
54
  logger = Logger.new(STDOUT)
55
- client = Klomp::Client.new(@uris, :logger=>logger).connect
56
-
55
+ client = Klomp::Client.new(@uris, :logger=>logger)
57
56
  assert_equal client.log, logger
58
-
59
- client.disconnect
60
57
  end
61
58
 
62
59
  it 'sends heartbeat' do
63
- client = Klomp::Client.new(@uris).connect.beat
60
+ client = Klomp::Client.new(@uris).connect
61
+ client.beat
62
+ client.disconnect
64
63
  end
65
64
 
66
65
  it 'sends requests and gets responses' do
@@ -84,7 +83,7 @@ describe Klomp::Client do
84
83
  client = Klomp::Client.new(@uris).connect
85
84
  reply_to_body = { 'reply_to_body' => rand(36**128).to_s(36) }
86
85
 
87
- client.puts(@destination, nil, { 'reply-to' => @destination })
86
+ client.send(@destination, nil, { 'reply-to' => @destination })
88
87
 
89
88
  got_message = false
90
89
  client.subscribe(@destination) do |msg|
@@ -114,5 +113,120 @@ describe Klomp::Client do
114
113
  client = Klomp::Client.new(@uris.first, :haz_cheezburgers => true, :retry_attempts => 42).connect
115
114
  assert client.write_conn.connected?
116
115
  assert_equal 42, client.write_conn.retry_attempts
116
+ client.disconnect
117
+ end
118
+
119
+ it 'uses a fibonacci back-off approach to reconnect' do
120
+ good_client = Object.new
121
+ def good_client.connect; true; end
122
+ def good_client.connected?; true; end
123
+ def good_client.connection; true; end
124
+
125
+ bad_client = Object.new
126
+ def bad_client.connect; raise "could not connect"; end
127
+ def bad_client.connected?; false; end
128
+
129
+ test_context = self
130
+ attempts = 0
131
+ conn = nil
132
+ fib = lambda {|n| (1..n).inject([0, 1]) {|fib,_| [fib[1], fib[0]+fib[1]]}.first}
133
+
134
+ pool_class = Class.new do
135
+ def initialize(*) end
136
+ def each(&blk) end
137
+ define_method :next_client do
138
+ attempts += 1
139
+ test_context.assert_equal fib[attempts], conn.retry_delay
140
+ if attempts == 6
141
+ good_client
142
+ else
143
+ bad_client
144
+ end
145
+ end
146
+ end
147
+
148
+ client = Klomp::Client.new(@uris.first, :pool => pool_class)
149
+ conn = client.write_conn
150
+ def conn.sleep_for_retry(*) end # skip sleep between retries for test
151
+
152
+ client.reconnect
153
+ assert_equal 6, attempts
154
+ end
155
+
156
+ it 'sends messages with uuids in the :id header' do
157
+ client = Klomp::Client.new(@uris, :translate_json => false).connect
158
+ client.send(@destination, '')
159
+
160
+ received_message = false
161
+ client.subscribe(@destination) do |msg|
162
+ received_message = msg
163
+ end
164
+ let_background_processor_run
165
+ assert received_message
166
+ assert received_message[:id], "message did not have an id"
167
+ assert received_message[:id] =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
168
+ "message id did not look like a uuid"
169
+
170
+ client.disconnect
171
+ end
172
+
173
+ it 'allows customization of the uuid generator' do
174
+ generator = Object.new
175
+ def generator.generate; "42"; end
176
+
177
+ client = Klomp::Client.new(@uris, :translate_json => false, :uuid => generator).connect
178
+ client.send(@destination, '')
179
+
180
+ received_message = false
181
+ client.subscribe(@destination) do |msg|
182
+ received_message = msg
183
+ end
184
+ let_background_processor_run
185
+ assert received_message
186
+ assert received_message[:id], "message did not have an id"
187
+ assert_equal "42", received_message[:id]
188
+
189
+ client.disconnect
190
+ end
191
+
192
+ it 'allows disabling generated message ids' do
193
+ client = Klomp::Client.new(@uris, :translate_json => false, :uuid => false).connect
194
+ client.send(@destination, '')
195
+
196
+ received_message = false
197
+ client.subscribe(@destination) do |msg|
198
+ received_message = msg
199
+ end
200
+ let_background_processor_run
201
+ assert received_message
202
+ refute received_message[:id], "message had an id"
203
+
204
+ client.disconnect
205
+ end
206
+
207
+ it 'logs message ids' do
208
+ logger = Object.new
209
+ def logger.msgs; @msgs; end
210
+ def logger.info(msg) (@msgs ||= []) << msg end
211
+
212
+ client = Klomp::Client.new(@uris, :translate_json => false, :logger => logger).connect
213
+ client.send(@destination, '')
214
+
215
+ received_message = false
216
+ client.subscribe(@destination) do |msg|
217
+ received_message = msg
218
+ end
219
+ let_background_processor_run
220
+ assert received_message
221
+ assert received_message[:id], "message did not have an id"
222
+
223
+ assert_equal 2, logger.msgs.length
224
+ assert logger.msgs[0] =~ /\[Sending\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
225
+ sent_id = $1
226
+ assert logger.msgs[1] =~ /\[Received\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
227
+ received_id = $1
228
+ assert_equal sent_id, received_id
229
+
230
+ client.disconnect
117
231
  end
118
232
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klomp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-21 00:00:00.000000000 Z
12
+ date: 2012-06-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: onstomp
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: uuid
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.5
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.5
46
62
  description: A simple wrapper around the OnStomp library with additional features
47
63
  email:
48
64
  - dev.happiness@livingsocial.com
@@ -78,7 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
94
  version: '0'
79
95
  segments:
80
96
  - 0
81
- hash: -1591973809162906904
97
+ hash: 395786091883431087
82
98
  required_rubygems_version: !ruby/object:Gem::Requirement
83
99
  none: false
84
100
  requirements: