klomp 0.0.3 → 0.0.4

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