message_bus 2.0.0.beta.4 → 2.0.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of message_bus might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d49bdd78a0d4f079d8e51b1025ce6a96b137585d
4
- data.tar.gz: 56e40e067dbb28f37cabe3e8c81b601b75291a11
3
+ metadata.gz: b91a7d621707536a33738a736ffd00bba4639d46
4
+ data.tar.gz: b5a445c066617c126b4bfaeb6444ad0f473ec670
5
5
  SHA512:
6
- metadata.gz: a827d7367f608068c934340736cce46e04464ffacae2f4c0d504efff10d9e184ab3ecaf575f6768931f5661186a48d080d9825f8f61f42f29595fc24cd148126
7
- data.tar.gz: aabd193e53d443f806745fe1d3bee1f7f4a494c9a511035d471c546a55ddeac78d18f3f11622c85994ef800169688c89fab19e2d612bc6acd32d9f51f49a8899
6
+ metadata.gz: adb5ec754024c6a62873a98dd2e3978d669f057bda86ffac0a6842787a9b8e14d0280315d6f499f40da9e86ed0a412c8ce0675262318f9330d023e2e58afe4c3
7
+ data.tar.gz: 50c1ee4d6b4e485198011c08a5632014321f34ceb388dd93568513d2762d1d9c539a0a3c7b4d2e6e74d7485e4ce5d3ace9d7f0e9bf8be6812df5f4d72f27c10a
data/CHANGELOG CHANGED
@@ -1,5 +1,10 @@
1
1
  29-02-2016
2
2
 
3
+ - Version 2.0.0.beta.5
4
+
5
+ - Fix: JavaScript unsubscribe was not updating publicly visible MessageBus.callbacks @sam
6
+ - Fix: When MessageBus is talking to a readonly redis buffering may cause a infinite loop @tgxworld
7
+
3
8
  - Version 2.0.0.beta.4
4
9
 
5
10
  - Feature: allow verbose redis logging by specifying `enable_redis_logger` in config, default disabled
data/README.md CHANGED
@@ -18,6 +18,10 @@ Live chat demo per [examples/chat](https://github.com/SamSaffron/message_bus/tre
18
18
 
19
19
  **Yes**, MessageBus uses Rack Hijack, this interface allows us to take control of the underlying socket. MessageBus can handle thousands of concurrent long polls on all popular Ruby webservers. MessageBus runs as middleware in your Rack (or by extension Rails) application and does not require a dedicated server. Background work is minimized to ensure it does not interfere with existing non MessageBus traffic.
20
20
 
21
+ ## Is this used in production at scale?
22
+
23
+ **Yes**, MessageBus was extracted out of [Discourse](http://www.discourse.org/) and is used in thousands of production Discourse sites at scale.
24
+
21
25
  ## Installation
22
26
 
23
27
  Add this line to your application's Gemfile:
@@ -216,7 +220,19 @@ message_bus also supports PostgreSQL as the backend:
216
220
  MessageBus.configure(backend: :postgres, backend_options: {user: 'message_bus', dbname: 'message_bus'})
217
221
  ```
218
222
 
219
- The PostgreSQL client message_bus uses is [ruby-pg](https://bitbucket.org/ged/ruby-pg), so you can visit it's repo to see what options you can configure.
223
+ The PostgreSQL client message_bus uses is [ruby-pg](https://bitbucket.org/ged/ruby-pg), so you can visit it's repo to see what options you can configure inside `:backend_options`.
224
+
225
+ A `:clear_every` option is also supported, which only clears the backlogs on every number of requests given. So if you set `clear_every: 100`, the backlog will only be cleared every 100 requests. This can improve performance in cases where exact backlog clearing are not required.
226
+
227
+ ### Memory
228
+
229
+ message_bus also supports an in-memory backend. This can be used for testing or simple single-process environments that do not require persistence.
230
+
231
+ ```ruby
232
+ MessageBus.configure(backend: :memory)
233
+ ```
234
+
235
+ The `:clear_every` option supported by the PostgreSQL backend is also supported by the in-memory backend.
220
236
 
221
237
  ### Forking/threading app servers
222
238
 
@@ -237,6 +253,23 @@ if defined?(PhusionPassenger)
237
253
  end
238
254
  ```
239
255
 
256
+ MessageBus uses long polling which needs to be configured in Passenger
257
+
258
+ * for passenger version < 5.0.21
259
+
260
+ `PhusionPassenger.advertised_concurrency_level = 0` to application.rb
261
+
262
+ * for passenger version > 5.0.21
263
+
264
+ ```
265
+ location /message-bus {
266
+ passenger_app_group_name foo_websocket;
267
+ passenger_force_max_concurrent_requests_per_process 0;
268
+ }
269
+ ```
270
+ to nginx.conf.
271
+ For more information see [Passenger documentation](https://www.phusionpassenger.com/library/config/nginx/tuning_sse_and_websockets/)
272
+
240
273
  #### Puma
241
274
  ```ruby
242
275
  # path/to/your/config/puma.rb
@@ -261,6 +294,7 @@ end
261
294
 
262
295
  If you are looking to contribute to this project here are some ideas
263
296
 
297
+ - Add a test suite for JavaScript message-bus.js
264
298
  - Build backends for other providers (zeromq, rabbitmq, disque)
265
299
  - Improve and properly document admin dashboard (add opt-in stats, better diagnostics into queues)
266
300
  - Improve general documentation (Add examples, refine existing examples)
data/Rakefile CHANGED
@@ -17,12 +17,16 @@ run_spec = proc do |backend|
17
17
  end
18
18
  end
19
19
 
20
- task :spec => [:spec_redis, :spec_postgres]
20
+ task :spec => [:spec_redis, :spec_postgres, :spec_memory]
21
21
 
22
22
  task :spec_redis do
23
23
  run_spec.call('redis')
24
24
  end
25
25
 
26
+ task :spec_memory do
27
+ run_spec.call('memory')
28
+ end
29
+
26
30
  task :spec_postgres do
27
31
  run_spec.call('postgres')
28
32
  end
@@ -345,22 +345,26 @@ window.MessageBus = (function() {
345
345
  last_id: lastId
346
346
  });
347
347
  if (me.longPoll) {
348
- return me.longPoll.abort();
348
+ me.longPoll.abort();
349
349
  }
350
+
351
+ return func;
350
352
  },
351
353
 
352
354
  // Unsubscribe from a channel
353
355
  unsubscribe: function(channel, func) {
354
- // TODO proper globbing
356
+ // TODO allow for globbing in the middle of a channel name
357
+ // like /something/*/something
358
+ // at the moment we only support globbing /something/*
355
359
  var glob;
356
360
  if (channel.indexOf("*", channel.length - 1) !== -1) {
357
361
  channel = channel.substr(0, channel.length - 1);
358
362
  glob = true;
359
363
  }
360
364
 
361
- var filtered = [];
365
+ var removed = false;
362
366
 
363
- for (var i=0; i<callbacks.length; i++) {
367
+ for (var i=callbacks.length-1; i>=0; i--) {
364
368
 
365
369
  callback = callbacks[i];
366
370
  var keep;
@@ -375,16 +379,17 @@ window.MessageBus = (function() {
375
379
  keep = true;
376
380
  }
377
381
 
378
- if (keep) {
379
- filtered.push(callback);
382
+ if (!keep) {
383
+ callbacks.splice(i,1);
384
+ removed = true;
380
385
  }
381
386
  }
382
387
 
383
- callbacks = filtered;
384
-
385
- if (me.longPoll) {
386
- return me.longPoll.abort();
388
+ if (removed && me.longPoll) {
389
+ me.longPoll.abort();
387
390
  }
391
+
392
+ return removed;
388
393
  }
389
394
  };
390
395
 
@@ -0,0 +1,3 @@
1
+ gem 'puma'
2
+ gem 'redis'
3
+ gem 'sinatra'
data/lib/message_bus.rb CHANGED
@@ -473,7 +473,7 @@ module MessageBus::Implementation
473
473
  globals, locals, local_globals, global_globals = nil
474
474
 
475
475
  @mutex.synchronize do
476
- raise MessageBus::BusDestroyed if @destroyed
476
+ return if @destroyed
477
477
  next unless @subscriptions
478
478
 
479
479
  globals = @subscriptions[nil]
@@ -0,0 +1,286 @@
1
+ module MessageBus::Memory; end
2
+
3
+ class MessageBus::Memory::Client
4
+ class Listener
5
+ attr_reader :do_sub, :do_unsub, :do_message
6
+
7
+ def subscribe(&block)
8
+ @do_sub = block
9
+ end
10
+
11
+ def unsubscribe(&block)
12
+ @do_unsub = block
13
+ end
14
+
15
+ def message(&block)
16
+ @do_message = block
17
+ end
18
+ end
19
+
20
+ def initialize(config)
21
+ @mutex = Mutex.new
22
+ @listeners = []
23
+ reset!
24
+ end
25
+
26
+ def add(channel, value)
27
+ listeners = nil
28
+ id = nil
29
+ sync do
30
+ id = @global_id += 1
31
+ chan(channel) << [id, value]
32
+ listeners = @listeners.dup
33
+ end
34
+ msg = MessageBus::Message.new id, id, channel, value
35
+ payload = msg.encode
36
+ listeners.each{|l| l.push(payload)}
37
+ id
38
+ end
39
+
40
+ def clear_global_backlog(backlog_id, num_to_keep)
41
+ if backlog_id > num_to_keep
42
+ oldest = backlog_id - num_to_keep
43
+ sync do
44
+ @channels.each_value do |entries|
45
+ entries.delete_if{|id, _| id <= oldest}
46
+ end
47
+ end
48
+ nil
49
+ end
50
+ end
51
+
52
+ def clear_channel_backlog(channel, backlog_id, num_to_keep)
53
+ oldest = backlog_id - num_to_keep
54
+ sync{chan(channel).delete_if{|id, _| id <= oldest}}
55
+ nil
56
+ end
57
+
58
+ def backlog(channel, backlog_id)
59
+ sync{chan(channel).select{|id, _| id > backlog_id}}
60
+ end
61
+
62
+ def global_backlog(backlog_id)
63
+ sync do
64
+ @channels.dup.flat_map do |channel, messages|
65
+ messages.select{|id, _| id > backlog_id}.map{|id, value| [id, channel, value]}
66
+ end.sort
67
+ end
68
+ end
69
+
70
+ def get_value(channel, id)
71
+ sync{chan(channel).find{|i, _| i == id}.last}
72
+ end
73
+
74
+ # Dangerous, drops the message_bus table containing the backlog if it exists.
75
+ def reset!
76
+ sync do
77
+ @global_id = 0
78
+ @channels = {}
79
+ end
80
+ end
81
+
82
+ def max_id(channel=nil)
83
+ if channel
84
+ sync do
85
+ if entry = chan(channel).last
86
+ entry.first
87
+ end
88
+ end
89
+ else
90
+ sync{@global_id - 1}
91
+ end || 0
92
+ end
93
+
94
+ def subscribe
95
+ listener = Listener.new
96
+ yield listener
97
+
98
+ q = Queue.new
99
+ sync do
100
+ @listeners << q
101
+ end
102
+
103
+ listener.do_sub.call
104
+ while msg = q.pop
105
+ listener.do_message.call(nil, msg)
106
+ end
107
+ listener.do_unsub.call
108
+ sync do
109
+ @listeners.delete(q)
110
+ end
111
+
112
+ nil
113
+ end
114
+
115
+ def unsubscribe
116
+ sync{@listeners.each{|l| l.push(nil)}}
117
+ end
118
+
119
+ private
120
+
121
+ def chan(channel)
122
+ @channels[channel] ||= []
123
+ end
124
+
125
+ def sync
126
+ @mutex.synchronize{yield}
127
+ end
128
+ end
129
+
130
+ class MessageBus::Memory::ReliablePubSub
131
+ attr_reader :subscribed
132
+ attr_accessor :max_backlog_size, :max_global_backlog_size, :clear_every
133
+
134
+ UNSUB_MESSAGE = "$$UNSUBSCRIBE"
135
+
136
+ # max_backlog_size is per multiplexed channel
137
+ def initialize(config = {}, max_backlog_size = 1000)
138
+ @config = config
139
+ @max_backlog_size = max_backlog_size
140
+ @max_global_backlog_size = 2000
141
+ # after 7 days inactive backlogs will be removed
142
+ @clear_every = config[:clear_every] || 1
143
+ end
144
+
145
+ def new_connection
146
+ MessageBus::Memory::Client.new(@config)
147
+ end
148
+
149
+ def backend
150
+ :memory
151
+ end
152
+
153
+ def after_fork
154
+ nil
155
+ end
156
+
157
+ def client
158
+ @client ||= new_connection
159
+ end
160
+
161
+ # use with extreme care, will nuke all of the data
162
+ def reset!
163
+ client.reset!
164
+ end
165
+
166
+ def publish(channel, data, queue_in_memory=true)
167
+ client = self.client
168
+ backlog_id = client.add(channel, data)
169
+ if backlog_id % clear_every == 0
170
+ client.clear_global_backlog(backlog_id, @max_global_backlog_size)
171
+ client.clear_channel_backlog(channel, backlog_id, @max_backlog_size)
172
+ end
173
+
174
+ backlog_id
175
+ end
176
+
177
+ def last_id(channel)
178
+ client.max_id(channel)
179
+ end
180
+
181
+ def backlog(channel, last_id = nil)
182
+ items = client.backlog channel, last_id.to_i
183
+
184
+ items.map! do |id, data|
185
+ MessageBus::Message.new id, id, channel, data
186
+ end
187
+ end
188
+
189
+ def global_backlog(last_id = nil)
190
+ last_id = last_id.to_i
191
+
192
+ items = client.global_backlog last_id.to_i
193
+
194
+ items.map! do |id, channel, data|
195
+ MessageBus::Message.new id, id, channel, data
196
+ end
197
+ end
198
+
199
+ def get_message(channel, message_id)
200
+ if data = client.get_value(channel, message_id)
201
+ MessageBus::Message.new message_id, message_id, channel, data
202
+ else
203
+ nil
204
+ end
205
+ end
206
+
207
+ def subscribe(channel, last_id = nil)
208
+ # trivial implementation for now,
209
+ # can cut down on connections if we only have one global subscriber
210
+ raise ArgumentError unless block_given?
211
+
212
+ global_subscribe(last_id) do |m|
213
+ yield m if m.channel == channel
214
+ end
215
+ end
216
+
217
+ def process_global_backlog(highest_id)
218
+ if highest_id > client.max_id
219
+ highest_id = 0
220
+ end
221
+
222
+ global_backlog(highest_id).each do |old|
223
+ yield old
224
+ highest_id = old.global_id
225
+ end
226
+
227
+ highest_id
228
+ end
229
+
230
+ def global_unsubscribe
231
+ client.unsubscribe
232
+ @subscribed = false
233
+ end
234
+
235
+ def global_subscribe(last_id=nil, &blk)
236
+ raise ArgumentError unless block_given?
237
+ highest_id = last_id
238
+
239
+ begin
240
+ client.subscribe do |on|
241
+ h = {}
242
+
243
+ on.subscribe do
244
+ if highest_id
245
+ process_global_backlog(highest_id) do |m|
246
+ h[m.global_id] = true
247
+ yield m
248
+ end
249
+ end
250
+ @subscribed = true
251
+ end
252
+
253
+ on.unsubscribe do
254
+ @subscribed = false
255
+ end
256
+
257
+ on.message do |c,m|
258
+ m = MessageBus::Message.decode m
259
+
260
+ # we have 3 options
261
+ #
262
+ # 1. message came in the correct order GREAT, just deal with it
263
+ # 2. message came in the incorrect order COMPLICATED, wait a tiny bit and clear backlog
264
+ # 3. message came in the incorrect order and is lowest than current highest id, reset
265
+
266
+ if h
267
+ # If already yielded during the clear backlog when subscribing,
268
+ # don't yield a duplicate copy.
269
+ unless h.delete(m.global_id)
270
+ h = nil if h.empty?
271
+ yield m
272
+ end
273
+ else
274
+ yield m
275
+ end
276
+ end
277
+ end
278
+ rescue => error
279
+ MessageBus.logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack\n#{error.backtrace.join("\n")}"
280
+ sleep 1
281
+ retry
282
+ end
283
+ end
284
+
285
+ MessageBus::BACKENDS[:memory] = self
286
+ end
@@ -103,25 +103,19 @@ class MessageBus::Postgres::Client
103
103
  sync{@listening_on[channel] = obj}
104
104
  listener = Listener.new
105
105
  yield listener
106
- pid = Process.pid
107
106
 
108
107
  conn = raw_pg_connection
109
108
  conn.exec "LISTEN #{channel}"
110
109
  listener.do_sub.call
111
110
  while listening_on?(channel, obj)
112
111
  conn.wait_for_notify(10) do |_,_,payload|
112
+ break unless listening_on?(channel, obj)
113
113
  listener.do_message.call(nil, payload)
114
114
  end
115
- break if pid != Process.pid
116
115
  end
117
116
  listener.do_unsub.call
118
117
 
119
- if pid != Process.pid
120
- sync{INHERITED_CONNECTIONS << conn}
121
- else
122
- conn.exec "UNLISTEN #{channel}"
123
- end
124
-
118
+ conn.exec "UNLISTEN #{channel}"
125
119
  nil
126
120
  end
127
121
 
@@ -139,7 +133,7 @@ class MessageBus::Postgres::Client
139
133
  end
140
134
 
141
135
  def create_table(conn)
142
- conn.exec 'CREATE TABLE message_bus (id bigserial PRIMARY KEY, channel text NOT NULL, value text NOT NULL, added_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL)'
136
+ conn.exec 'CREATE TABLE message_bus (id bigserial PRIMARY KEY, channel text NOT NULL, value text NOT NULL CHECK (octet_length(value) >= 2), added_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL)'
143
137
  conn.exec 'CREATE INDEX table_channel_id_index ON message_bus (channel, id)'
144
138
  conn.exec 'CREATE INDEX table_added_at_index ON message_bus (added_at)'
145
139
  nil
@@ -150,7 +144,6 @@ class MessageBus::Postgres::Client
150
144
  if current_pid != @pid
151
145
  @pid = current_pid
152
146
  sync do
153
- @listening_on.clear
154
147
  INHERITED_CONNECTIONS.concat(@available)
155
148
  @available.clear
156
149
  end
@@ -214,9 +207,7 @@ end
214
207
 
215
208
  class MessageBus::Postgres::ReliablePubSub
216
209
  attr_reader :subscribed
217
- attr_accessor :max_publish_retries, :max_publish_wait, :max_backlog_size,
218
- :max_global_backlog_size, :max_in_memory_publish_backlog,
219
- :max_backlog_age
210
+ attr_accessor :max_backlog_size, :max_global_backlog_size, :max_backlog_age, :clear_every
220
211
 
221
212
  UNSUB_MESSAGE = "$$UNSUBSCRIBE"
222
213
 
@@ -229,15 +220,9 @@ class MessageBus::Postgres::ReliablePubSub
229
220
  @config = config
230
221
  @max_backlog_size = max_backlog_size
231
222
  @max_global_backlog_size = 2000
232
- @max_publish_retries = 10
233
- @max_publish_wait = 500 #ms
234
- @max_in_memory_publish_backlog = 1000
235
- @in_memory_backlog = []
236
- @lock = Mutex.new
237
- @flush_backlog_thread = nil
238
223
  # after 7 days inactive backlogs will be removed
239
224
  @max_backlog_age = 604800
240
- @h = {}
225
+ @clear_every = config[:clear_every] || 1
241
226
  end
242
227
 
243
228
  def new_connection
@@ -272,9 +257,11 @@ class MessageBus::Postgres::ReliablePubSub
272
257
  msg = MessageBus::Message.new backlog_id, backlog_id, channel, data
273
258
  payload = msg.encode
274
259
  client.publish postgresql_channel_name, payload
275
- client.clear_global_backlog(backlog_id, @max_global_backlog_size)
276
- client.expire(@max_backlog_age)
277
- client.clear_channel_backlog(channel, backlog_id, @max_backlog_size)
260
+ if backlog_id % clear_every == 0
261
+ client.clear_global_backlog(backlog_id, @max_global_backlog_size)
262
+ client.expire(@max_backlog_age)
263
+ client.clear_channel_backlog(channel, backlog_id, @max_backlog_size)
264
+ end
278
265
 
279
266
  backlog_id
280
267
  end
@@ -10,9 +10,7 @@ require 'redis'
10
10
  module MessageBus::Redis; end
11
11
  class MessageBus::Redis::ReliablePubSub
12
12
  attr_reader :subscribed
13
- attr_accessor :max_publish_retries, :max_publish_wait, :max_backlog_size,
14
- :max_global_backlog_size, :max_in_memory_publish_backlog,
15
- :max_backlog_age
13
+ attr_accessor :max_backlog_size, :max_global_backlog_size, :max_in_memory_publish_backlog, :max_backlog_age
16
14
 
17
15
  UNSUB_MESSAGE = "$$UNSUBSCRIBE"
18
16
 
@@ -33,8 +31,6 @@ class MessageBus::Redis::ReliablePubSub
33
31
  end
34
32
  @max_backlog_size = max_backlog_size
35
33
  @max_global_backlog_size = 2000
36
- @max_publish_retries = 10
37
- @max_publish_wait = 500 #ms
38
34
  @max_in_memory_publish_backlog = 1000
39
35
  @in_memory_backlog = []
40
36
  @lock = Mutex.new
@@ -151,11 +147,21 @@ class MessageBus::Redis::ReliablePubSub
151
147
  end
152
148
 
153
149
  def ensure_backlog_flushed
154
- while true
150
+ flushed = false
151
+
152
+ while !flushed
155
153
  try_again = false
156
154
 
155
+ if is_readonly?
156
+ sleep 1
157
+ next
158
+ end
159
+
157
160
  @lock.synchronize do
158
- break if @in_memory_backlog.length == 0
161
+ if @in_memory_backlog.length == 0
162
+ flushed = true
163
+ break
164
+ end
159
165
 
160
166
  begin
161
167
  publish(*@in_memory_backlog[0],false)
@@ -171,13 +177,6 @@ class MessageBus::Redis::ReliablePubSub
171
177
 
172
178
  @in_memory_backlog.delete_at(0) unless try_again
173
179
  end
174
-
175
- if try_again
176
- sleep 0.005
177
- # in case we are not connected to the correct server
178
- # which can happen when sharing ips
179
- pub_redis.client.reconnect
180
- end
181
180
  end
182
181
  ensure
183
182
  @lock.synchronize do
@@ -342,5 +341,21 @@ class MessageBus::Redis::ReliablePubSub
342
341
  end
343
342
  end
344
343
 
344
+ private
345
+
346
+ def is_readonly?
347
+ key = "__mb_is_readonly".freeze
348
+
349
+ begin
350
+ # in case we are not connected to the correct server
351
+ # which can happen when sharing ips
352
+ pub_redis.client.reconnect
353
+ pub_redis.client.call([:set, key, '1'])
354
+ false
355
+ rescue Redis::CommandError => e
356
+ return true if e.message =~ /^READONLY/
357
+ end
358
+ end
359
+
345
360
  MessageBus::BACKENDS[:redis] = self
346
361
  end
@@ -1,3 +1,3 @@
1
1
  module MessageBus
2
- VERSION = "2.0.0.beta.4"
2
+ VERSION = "2.0.0.beta.5"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require_relative '../../../spec_helper'
2
2
  require 'message_bus'
3
3
 
4
- if MESSAGE_BUS_CONFIG[:backend] == :postgres
4
+ if MESSAGE_BUS_CONFIG[:backend] == :postgres || MESSAGE_BUS_CONFIG[:backend] == :memory
5
5
  describe PUB_SUB_CLASS do
6
6
 
7
7
  def new_test_bus
@@ -56,24 +56,37 @@ describe PUB_SUB_CLASS do
56
56
  it "should truncate channels correctly" do
57
57
  @bus.max_backlog_size = 2
58
58
  4.times do |t|
59
- @bus.publish "/foo", t.to_s
59
+ @bus.publish "/foo", "1#{t}"
60
60
  end
61
61
 
62
62
  @bus.backlog("/foo").to_a.must_equal [
63
- MessageBus::Message.new(3,3,'/foo','2'),
64
- MessageBus::Message.new(4,4,'/foo','3'),
63
+ MessageBus::Message.new(3,3,'/foo','12'),
64
+ MessageBus::Message.new(4,4,'/foo','13'),
65
65
  ]
66
66
  end
67
67
 
68
68
  it "should truncate global backlog correctly" do
69
69
  @bus.max_global_backlog_size = 2
70
- @bus.publish "/foo", "1"
71
- @bus.publish "/bar", "2"
72
- @bus.publish "/baz", "3"
70
+ @bus.publish "/foo", "11"
71
+ @bus.publish "/bar", "21"
72
+ @bus.publish "/baz", "31"
73
73
 
74
74
  @bus.global_backlog.length.must_equal 2
75
75
  end
76
76
 
77
+ it "should support clear_every setting" do
78
+ @bus.clear_every = 5
79
+ @bus.max_global_backlog_size = 2
80
+ @bus.publish "/foo", "11"
81
+ @bus.publish "/bar", "21"
82
+ @bus.publish "/baz", "31"
83
+ @bus.publish "/bar", "41"
84
+ @bus.global_backlog.length.must_equal 4
85
+
86
+ @bus.publish "/baz", "51"
87
+ @bus.global_backlog.length.must_equal 2
88
+ end
89
+
77
90
  it "should be able to grab a message by id" do
78
91
  id1 = @bus.publish "/foo", "bar"
79
92
  id2 = @bus.publish "/foo", "baz"
@@ -97,14 +110,14 @@ describe PUB_SUB_CLASS do
97
110
 
98
111
  it "should correctly omit dropped messages from the global backlog" do
99
112
  @bus.max_backlog_size = 1
100
- @bus.publish "/foo", "a"
101
- @bus.publish "/foo", "b"
102
- @bus.publish "/bar", "a"
103
- @bus.publish "/bar", "b"
113
+ @bus.publish "/foo", "a1"
114
+ @bus.publish "/foo", "b1"
115
+ @bus.publish "/bar", "a1"
116
+ @bus.publish "/bar", "b1"
104
117
 
105
118
  @bus.global_backlog.to_a.must_equal [
106
- MessageBus::Message.new(2, 2, "/foo", "b"),
107
- MessageBus::Message.new(4, 4, "/bar", "b")
119
+ MessageBus::Message.new(2, 2, "/foo", "b1"),
120
+ MessageBus::Message.new(4, 4, "/bar", "b1")
108
121
  ]
109
122
  end
110
123
 
@@ -112,9 +125,9 @@ describe PUB_SUB_CLASS do
112
125
  threads = []
113
126
  4.times do
114
127
  threads << Thread.new do
115
- bus = new_test_bus
128
+ bus = @bus
116
129
  25.times {
117
- bus.publish "/foo", "."
130
+ bus.publish "/foo", ".."
118
131
  }
119
132
  end
120
133
  end
@@ -124,17 +137,17 @@ describe PUB_SUB_CLASS do
124
137
  end
125
138
 
126
139
  it "should be able to subscribe globally with recovery" do
127
- @bus.publish("/foo", "1")
128
- @bus.publish("/bar", "2")
140
+ @bus.publish("/foo", "11")
141
+ @bus.publish("/bar", "12")
129
142
  got = []
130
143
 
131
144
  t = Thread.new do
132
- new_test_bus.global_subscribe(0) do |msg|
145
+ @bus.global_subscribe(0) do |msg|
133
146
  got << msg
134
147
  end
135
148
  end
136
149
 
137
- @bus.publish("/bar", "3")
150
+ @bus.publish("/bar", "13")
138
151
 
139
152
  wait_for(100) do
140
153
  got.length == 3
@@ -143,7 +156,7 @@ describe PUB_SUB_CLASS do
143
156
  t.kill
144
157
 
145
158
  got.length.must_equal 3
146
- got.map{|m| m.data}.must_equal ["1","2","3"]
159
+ got.map{|m| m.data}.must_equal ["11","12","13"]
147
160
  end
148
161
 
149
162
  it "should be able to encode and decode messages properly" do
@@ -152,17 +165,17 @@ describe PUB_SUB_CLASS do
152
165
  end
153
166
 
154
167
  it "should handle subscribe on single channel, with recovery" do
155
- @bus.publish("/foo", "1")
156
- @bus.publish("/bar", "2")
168
+ @bus.publish("/foo", "11")
169
+ @bus.publish("/bar", "12")
157
170
  got = []
158
171
 
159
172
  t = Thread.new do
160
- new_test_bus.subscribe("/foo",0) do |msg|
173
+ @bus.subscribe("/foo",0) do |msg|
161
174
  got << msg
162
175
  end
163
176
  end
164
177
 
165
- @bus.publish("/foo", "3")
178
+ @bus.publish("/foo", "13")
166
179
 
167
180
  wait_for(100) do
168
181
  got.length == 2
@@ -170,15 +183,15 @@ describe PUB_SUB_CLASS do
170
183
 
171
184
  t.kill
172
185
 
173
- got.map{|m| m.data}.must_equal ["1","3"]
186
+ got.map{|m| m.data}.must_equal ["11","13"]
174
187
  end
175
188
 
176
189
  it "should not get backlog if subscribe is called without params" do
177
- @bus.publish("/foo", "1")
190
+ @bus.publish("/foo", "11")
178
191
  got = []
179
192
 
180
193
  t = Thread.new do
181
- new_test_bus.subscribe("/foo") do |msg|
194
+ @bus.subscribe("/foo") do |msg|
182
195
  got << msg
183
196
  end
184
197
  end
@@ -187,7 +200,7 @@ describe PUB_SUB_CLASS do
187
200
  # I thought about adding a subscribed callback, but outside of testing it matters less
188
201
  sleep 0.05
189
202
 
190
- @bus.publish("/foo", "2")
203
+ @bus.publish("/foo", "12")
191
204
 
192
205
  wait_for(100) do
193
206
  got.length == 1
@@ -195,12 +208,12 @@ describe PUB_SUB_CLASS do
195
208
 
196
209
  t.kill
197
210
 
198
- got.map{|m| m.data}.must_equal ["2"]
211
+ got.map{|m| m.data}.must_equal ["12"]
199
212
  end
200
213
 
201
214
  it "should allow us to get last id on a channel" do
202
215
  @bus.last_id("/foo").must_equal 0
203
- @bus.publish("/foo", "1")
216
+ @bus.publish("/foo", "11")
204
217
  @bus.last_id("/foo").must_equal 1
205
218
  end
206
219
 
@@ -26,16 +26,22 @@ describe PUB_SUB_CLASS do
26
26
  @bus.pub_redis.slaveof "127.0.0.80", "666"
27
27
  @bus.max_in_memory_publish_backlog = 2
28
28
 
29
+ current_threads = Thread.list
30
+ current_threads_length = current_threads.count
31
+
29
32
  3.times do
30
33
  result = @bus.publish "/foo", "bar"
31
34
  result.must_equal nil
35
+ Thread.list.length.must_equal (current_threads_length + 1)
32
36
  end
33
37
 
34
38
  @bus.pub_redis.slaveof "no", "one"
35
39
  sleep 0.01
36
40
 
37
- @bus.backlog("/foo", 0).map(&:data).must_equal ["bar","bar"]
41
+ (Thread.list - current_threads).each(&:join)
42
+ Thread.list.length.must_equal current_threads_length
38
43
 
44
+ @bus.backlog("/foo", 0).map(&:data).must_equal ["bar","bar"]
39
45
  end
40
46
  end
41
47
 
@@ -7,7 +7,7 @@ describe MessageBus::Client do
7
7
 
8
8
  before do
9
9
  @bus = MessageBus::Instance.new
10
- @bus.redis_config = MESSAGE_BUS_CONFIG
10
+ @bus.configure(MESSAGE_BUS_CONFIG)
11
11
  @client = MessageBus::Client.new client_id: 'abc', message_bus: @bus
12
12
  end
13
13
 
@@ -1,6 +1,7 @@
1
1
  require_relative '../../spec_helper'
2
2
  require 'message_bus'
3
3
 
4
+ unless MESSAGE_BUS_CONFIG[:backend] == :memory
4
5
  describe PUB_SUB_CLASS do
5
6
  def self.error!
6
7
  @error = true
@@ -44,7 +45,7 @@ describe PUB_SUB_CLASS do
44
45
  new_bus.reset!
45
46
  begin
46
47
  pids = (1..10).map{spawn_child}
47
- expected_responses = pids.map{|x| (0...10).map{|i| "#{i}-#{x}"}}.flatten
48
+ expected_responses = pids.map{|x| (0...10).map{|i| "0#{i}-#{x}"}}.flatten
48
49
  unexpected_responses = []
49
50
  bus = new_bus
50
51
  t = Thread.new do
@@ -56,7 +57,7 @@ describe PUB_SUB_CLASS do
56
57
  end
57
58
  end
58
59
  end
59
- 10.times{|i| bus.publish("/echo", i.to_s)}
60
+ 10.times{|i| bus.publish("/echo", "0#{i}")}
60
61
  wait_for 4000 do
61
62
  expected_responses.empty?
62
63
  end
@@ -83,3 +84,4 @@ describe PUB_SUB_CLASS do
83
84
  end
84
85
  end
85
86
  end
87
+ end
@@ -10,7 +10,7 @@ describe MessageBus::Rack::Middleware do
10
10
 
11
11
  before do
12
12
  bus = @bus = MessageBus::Instance.new
13
- @bus.redis_config = MESSAGE_BUS_CONFIG
13
+ @bus.configure(MESSAGE_BUS_CONFIG)
14
14
  @bus.long_polling_enabled = false
15
15
 
16
16
  e_m = extra_middleware
@@ -23,9 +23,10 @@ describe MessageBus::Rack::Middleware do
23
23
 
24
24
  @async_middleware = builder.to_app
25
25
  @message_bus_middleware = @async_middleware.app
26
+ @bus.reset!
26
27
  end
27
28
 
28
- after do |x|
29
+ after do
29
30
  @message_bus_middleware.stop_listener
30
31
  @bus.reset!
31
32
  @bus.destroy
@@ -10,7 +10,7 @@ describe MessageBus do
10
10
  @bus.site_id_lookup do
11
11
  "magic"
12
12
  end
13
- @bus.redis_config = MESSAGE_BUS_CONFIG
13
+ @bus.configure(MESSAGE_BUS_CONFIG)
14
14
  end
15
15
 
16
16
  after do
@@ -43,11 +43,11 @@ describe MessageBus do
43
43
  @bus.publish("/chuck", {:norris => true})
44
44
  @bus.publish("/chuck", {:norris => true})
45
45
 
46
- @bus.reliable_pub_sub.pub_redis.flushall
46
+ @bus.reliable_pub_sub.reset!
47
47
 
48
48
  @bus.publish("/chuck", {:yeager => true})
49
49
 
50
- wait_for(2000){ data["yeager"]}
50
+ wait_for(2000){ data && data["yeager"]}
51
51
 
52
52
  data["yeager"].must_equal true
53
53
 
@@ -184,39 +184,40 @@ describe MessageBus do
184
184
 
185
185
  end
186
186
 
187
- it "should support forking properly do" do
188
- data = nil
189
- @bus.subscribe do |msg|
190
- data = msg.data
191
- end
192
-
193
- @bus.publish("/hello", "world")
194
-
195
- wait_for(2000){ data }
187
+ unless MESSAGE_BUS_CONFIG[:backend] == :memory
188
+ it "should support forking properly do" do
189
+ data = nil
190
+ @bus.subscribe do |msg|
191
+ data = msg.data
192
+ end
196
193
 
197
- if child = Process.fork
198
- wait_for(2000) { data == "ready" }
199
- @bus.publish("/hello", "world1")
200
- wait_for(2000) { data == "got it" }
201
- data.must_equal "got it"
202
- Process.wait(child)
203
- else
204
- begin
205
- @bus.after_fork
206
- @bus.publish("/hello", "ready")
207
- wait_for(2000) { data == "world1" }
208
- if(data=="world1")
209
- @bus.publish("/hello", "got it")
194
+ @bus.publish("/hello", "world")
195
+
196
+ wait_for(2000){ data }
197
+
198
+ if child = Process.fork
199
+ wait_for(2000) { data == "ready" }
200
+ @bus.publish("/hello", "world1")
201
+ wait_for(2000) { data == "got it" }
202
+ data.must_equal "got it"
203
+ Process.wait(child)
204
+ else
205
+ begin
206
+ @bus.after_fork
207
+ @bus.publish("/hello", "ready")
208
+ wait_for(2000) { data == "world1" }
209
+ if(data=="world1")
210
+ @bus.publish("/hello", "got it")
211
+ end
212
+
213
+ $stdout.reopen("/dev/null", "w")
214
+ $stderr.reopen("/dev/null", "w")
215
+
216
+ ensure
217
+ exit!(0)
210
218
  end
211
-
212
- $stdout.reopen("/dev/null", "w")
213
- $stderr.reopen("/dev/null", "w")
214
-
215
- ensure
216
- exit!(0)
217
219
  end
218
- end
219
220
 
221
+ end
220
222
  end
221
-
222
223
  end
@@ -345,22 +345,26 @@ window.MessageBus = (function() {
345
345
  last_id: lastId
346
346
  });
347
347
  if (me.longPoll) {
348
- return me.longPoll.abort();
348
+ me.longPoll.abort();
349
349
  }
350
+
351
+ return func;
350
352
  },
351
353
 
352
354
  // Unsubscribe from a channel
353
355
  unsubscribe: function(channel, func) {
354
- // TODO proper globbing
356
+ // TODO allow for globbing in the middle of a channel name
357
+ // like /something/*/something
358
+ // at the moment we only support globbing /something/*
355
359
  var glob;
356
360
  if (channel.indexOf("*", channel.length - 1) !== -1) {
357
361
  channel = channel.substr(0, channel.length - 1);
358
362
  glob = true;
359
363
  }
360
364
 
361
- var filtered = [];
365
+ var removed = false;
362
366
 
363
- for (var i=0; i<callbacks.length; i++) {
367
+ for (var i=callbacks.length-1; i>=0; i--) {
364
368
 
365
369
  callback = callbacks[i];
366
370
  var keep;
@@ -375,16 +379,17 @@ window.MessageBus = (function() {
375
379
  keep = true;
376
380
  }
377
381
 
378
- if (keep) {
379
- filtered.push(callback);
382
+ if (!keep) {
383
+ callbacks.splice(i,1);
384
+ removed = true;
380
385
  }
381
386
  }
382
387
 
383
- callbacks = filtered;
384
-
385
- if (me.longPoll) {
386
- return me.longPoll.abort();
388
+ if (removed && me.longPoll) {
389
+ me.longPoll.abort();
387
390
  }
391
+
392
+ return removed;
388
393
  }
389
394
  };
390
395
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: message_bus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta.4
4
+ version: 2.0.0.beta.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-28 00:00:00.000000000 Z
11
+ date: 2016-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -79,6 +79,7 @@ files:
79
79
  - examples/bench/puma.rb
80
80
  - examples/bench/unicorn.conf.rb
81
81
  - examples/bench/wrk.sample
82
+ - examples/chat/Gemfile
82
83
  - examples/chat/chat.rb
83
84
  - examples/chat/config.ru
84
85
  - examples/chat/docker_container/chat.yml
@@ -86,6 +87,7 @@ files:
86
87
  - examples/minimal/Gemfile
87
88
  - examples/minimal/config.ru
88
89
  - lib/message_bus.rb
90
+ - lib/message_bus/backends/memory.rb
89
91
  - lib/message_bus/backends/postgres.rb
90
92
  - lib/message_bus/backends/redis.rb
91
93
  - lib/message_bus/client.rb