message_bus 0.9.3.2 → 0.9.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.

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: cc56c6496f2994b9144a4c546d588784d5d8f818
4
- data.tar.gz: e6ffaf32700df3a6628fa858ba24596c898a3dc5
3
+ metadata.gz: bd00357fe6b7e4663c46c3c34c74a5d8529433c5
4
+ data.tar.gz: df3641a9d63749c6035ecd0b45d656bd53361e4d
5
5
  SHA512:
6
- metadata.gz: 470786eff06753a648d739f4af5b45175139fb3d2a3bae53660d139cfb2768fe8c2272fec7c1b4b2163d95b1d246a3f53300262c2044baa9894ffa7843531228
7
- data.tar.gz: b980a483a53b5665f83b36db886a2e8566ab2900b9a5d021ece1373afbb3812649c7254f0228a2d0a52a80ab793d60eca83b66a826fbb97ffafb67b878a60e9b
6
+ metadata.gz: c08c802702acc7c30db0c3ebfb1c0949644553b7fc6a63330a8f260d414a350e94c2fb40143bd3956a068eda1a8e764e3fc3f858568bedb5075d25d82392185d
7
+ data.tar.gz: 41c4d7584df49be995a21495841d63d65998aab5582c5ec6987fc620f86b995cc632e505fa641558c15568d8e6545075d075f4eec1c6501ad21fc4563220dc99
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ 13-01-2014
2
+ - Version 0.9.4
3
+ - Added support for /global/ channel to publish messages across a multisite
4
+ - Cleaned up test harness so it uses local bus as opposed to global
5
+ - Fix bug where we could subscribe to a channel but miss starting messages
6
+ - Added method for destroying a local MessageBus instance
7
+ - ensure_reactor could say the reactor is running, but it was not, on first call
8
+
1
9
  06-12-2013
2
10
  - Version 0.9.3.2
3
11
  - Fix permissions in gem
data/Guardfile CHANGED
@@ -1,4 +1,4 @@
1
- guard 'rspec', :focus_on_failed => true do
1
+ guard 'rspec' do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
3
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
4
  watch('spec/spec_helper.rb') { "spec" }
data/README.md CHANGED
@@ -60,6 +60,23 @@ end
60
60
 
61
61
  ```
62
62
 
63
+ ### Multisite support
64
+
65
+ MessageBus can be used in an environment that hosts multiple sites by multiplexing channels. To use this mode
66
+
67
+ ```
68
+ # define a site_id lookup method
69
+ MessageBus.site_id_lookup do
70
+ some_method_that_returns_site_id_string
71
+ end
72
+
73
+ # you may post messages just to this site
74
+ MessageBus.publish "/channel", "some message"
75
+
76
+ # you may publish messages to ALL sites using the /global/ prefix
77
+ MessageBus.publish "/global/channel", "will go to all sites"
78
+ ```
79
+
63
80
  JavaScript can listen on any channel (and receive notification via polling or long polling):
64
81
 
65
82
  ```html
@@ -119,6 +119,7 @@ window.MessageBus = (function() {
119
119
  },
120
120
  success: function(messages) {
121
121
  failCount = 0;
122
+ if (messages === null) return; // server unexpectedly closed connection
122
123
  $.each(messages,function(_,message) {
123
124
  gotData = true;
124
125
  $.each(callbacks, function(_,callback) {
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'message_bus'
@@ -0,0 +1,7 @@
1
+ require 'message_bus'
2
+
3
+ #MessageBus.long_polling_interval = 1000 * 2
4
+
5
+ use MessageBus::Rack::Middleware
6
+ run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Howdy"]] }
7
+
data/lib/message_bus.rb CHANGED
@@ -12,6 +12,7 @@ require "message_bus/message_handler"
12
12
  require "message_bus/diagnostics"
13
13
  require "message_bus/rack/middleware"
14
14
  require "message_bus/rack/diagnostics"
15
+ require "monitor.rb"
15
16
 
16
17
  # we still need to take care of the logger
17
18
  if defined?(::Rails)
@@ -19,8 +20,20 @@ if defined?(::Rails)
19
20
  end
20
21
 
21
22
  module MessageBus; end
23
+ class MessageBus::InvalidMessage < Exception; end
24
+ class MessageBus::BusDestroyed < Exception; end
25
+
22
26
  module MessageBus::Implementation
23
27
 
28
+ # Like Mutex but safe for recursive calls
29
+ class Synchronizer
30
+ include MonitorMixin
31
+ end
32
+
33
+ def initialize
34
+ @mutex = Synchronizer.new
35
+ end
36
+
24
37
  def cache_assets=(val)
25
38
  @cache_assets = val
26
39
  end
@@ -164,7 +177,10 @@ module MessageBus::Implementation
164
177
  end
165
178
 
166
179
  def reliable_pub_sub
167
- @reliable_pub_sub ||= MessageBus::ReliablePubSub.new redis_config
180
+ @mutex.synchronize do
181
+ return nil if @destroyed
182
+ @reliable_pub_sub ||= MessageBus::ReliablePubSub.new redis_config
183
+ end
168
184
  end
169
185
 
170
186
  def enable_diagnostics
@@ -173,6 +189,9 @@ module MessageBus::Implementation
173
189
 
174
190
  def publish(channel, data, opts = nil)
175
191
  return if @off
192
+ @mutex.synchronize do
193
+ raise ::MessageBus::BusDestroyed if @destroyed
194
+ end
176
195
 
177
196
  user_ids = nil
178
197
  group_ids = nil
@@ -181,6 +200,8 @@ module MessageBus::Implementation
181
200
  group_ids = opts[:group_ids]
182
201
  end
183
202
 
203
+ raise ::MessageBus::InvalidMessage if (user_ids || group_ids) && global?(channel)
204
+
184
205
  encoded_data = JSON.dump({
185
206
  data: data,
186
207
  user_ids: user_ids,
@@ -202,7 +223,7 @@ module MessageBus::Implementation
202
223
 
203
224
  # encode channel name to include site
204
225
  def encode_channel_name(channel)
205
- if site_id_lookup
226
+ if site_id_lookup && !global?(channel)
206
227
  raise ArgumentError.new channel if channel.include? ENCODE_SITE_TOKEN
207
228
  "#{channel}#{ENCODE_SITE_TOKEN}#{site_id_lookup.call}"
208
229
  else
@@ -229,7 +250,7 @@ module MessageBus::Implementation
229
250
 
230
251
  # subscribe only on current site
231
252
  def local_subscribe(channel=nil, &blk)
232
- site_id = site_id_lookup.call if site_id_lookup
253
+ site_id = site_id_lookup.call if site_id_lookup && ! global?(channel)
233
254
  subscribe_impl(channel, site_id, &blk)
234
255
  end
235
256
 
@@ -238,7 +259,7 @@ module MessageBus::Implementation
238
259
  if channel
239
260
  reliable_pub_sub.backlog(encode_channel_name(channel), last_id)
240
261
  else
241
- reliable_pub_sub.global_backlog(encode_channel_name(channel), last_id)
262
+ reliable_pub_sub.global_backlog(last_id)
242
263
  end
243
264
 
244
265
  old.each{ |m|
@@ -247,14 +268,26 @@ module MessageBus::Implementation
247
268
  old
248
269
  end
249
270
 
250
-
251
271
  def last_id(channel)
252
272
  reliable_pub_sub.last_id(encode_channel_name(channel))
253
273
  end
254
274
 
275
+ def last_message(channel)
276
+ if last_id = last_id(channel)
277
+ messages = backlog(channel, last_id-1)
278
+ if messages
279
+ messages[0]
280
+ end
281
+ end
282
+ end
255
283
 
256
284
  def destroy
257
- reliable_pub_sub.global_unsubscribe
285
+ @mutex.synchronize do
286
+ @subscriptions ||= {}
287
+ reliable_pub_sub.global_unsubscribe
288
+ @destroyed = true
289
+ end
290
+ @subscriber_thread.join if @subscriber_thread
258
291
  end
259
292
 
260
293
  def after_fork
@@ -262,8 +295,21 @@ module MessageBus::Implementation
262
295
  ensure_subscriber_thread
263
296
  end
264
297
 
298
+ def listening?
299
+ @subscriber_thread && @subscriber_thread.alive?
300
+ end
301
+
302
+ # will reset all keys
303
+ def reset!
304
+ reliable_pub_sub.reset!
305
+ end
306
+
265
307
  protected
266
308
 
309
+ def global?(channel)
310
+ channel && channel.start_with?('/global/'.freeze)
311
+ end
312
+
267
313
  def decode_message!(msg)
268
314
  channel, site_id = decode_channel_name(msg.channel)
269
315
  msg.channel = channel
@@ -275,15 +321,27 @@ module MessageBus::Implementation
275
321
  end
276
322
 
277
323
  def subscribe_impl(channel, site_id, &blk)
324
+
325
+ raise MessageBus::BusDestroyed if @destroyed
326
+
278
327
  @subscriptions ||= {}
279
328
  @subscriptions[site_id] ||= {}
280
329
  @subscriptions[site_id][channel] ||= []
281
330
  @subscriptions[site_id][channel] << blk
282
331
  ensure_subscriber_thread
332
+
333
+ attempts = 100
334
+ while attempts > 0 && !reliable_pub_sub.subscribed
335
+ sleep 0.001
336
+ attempts-=1
337
+ end
338
+
339
+ raise MessageBus::BusDestroyed if @destroyed
283
340
  blk
284
341
  end
285
342
 
286
343
  def unsubscribe_impl(channel, site_id, &blk)
344
+
287
345
  @mutex.synchronize do
288
346
  if blk
289
347
  @subscriptions[site_id][channel].delete blk
@@ -291,42 +349,50 @@ module MessageBus::Implementation
291
349
  @subscriptions[site_id][channel] = []
292
350
  end
293
351
  end
352
+
294
353
  end
295
354
 
296
355
 
297
356
  def ensure_subscriber_thread
298
- @mutex ||= Mutex.new
299
357
  @mutex.synchronize do
300
- return if @subscriber_thread && @subscriber_thread.alive?
301
- @subscriber_thread = Thread.new do
302
- reliable_pub_sub.global_subscribe do |msg|
303
- begin
304
- decode_message!(msg)
358
+ return if (@subscriber_thread && @subscriber_thread.alive?) || @destroyed
359
+ @subscriber_thread = new_subscriber_thread
360
+ end
361
+ end
305
362
 
306
- @mutex.synchronize do
307
- globals = @subscriptions[nil]
308
- locals = @subscriptions[msg.site_id] if msg.site_id
363
+ def new_subscriber_thread
364
+ Thread.new do
365
+ global_subscribe_thread unless @destroyed
366
+ end
367
+ end
309
368
 
310
- global_globals = globals[nil] if globals
311
- local_globals = locals[nil] if locals
369
+ def global_subscribe_thread
370
+ reliable_pub_sub.global_subscribe do |msg|
371
+ begin
372
+ decode_message!(msg)
373
+ globals, locals, local_globals, global_globals = nil
312
374
 
313
- globals = globals[msg.channel] if globals
314
- locals = locals[msg.channel] if locals
375
+ @mutex.synchronize do
376
+ raise MessageBus::BusDestroyed if @destroyed
377
+ globals = @subscriptions[nil]
378
+ locals = @subscriptions[msg.site_id] if msg.site_id
315
379
 
316
- multi_each(globals,locals, global_globals, local_globals) do |c|
317
- begin
318
- c.call msg
319
- rescue => e
320
- MessageBus.logger.warn "failed to deliver message, skipping #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
321
- end
322
- end
323
- end
380
+ global_globals = globals[nil] if globals
381
+ local_globals = locals[nil] if locals
324
382
 
383
+ globals = globals[msg.channel] if globals
384
+ locals = locals[msg.channel] if locals
385
+ end
386
+
387
+ multi_each(globals,locals, global_globals, local_globals) do |c|
388
+ begin
389
+ c.call msg
325
390
  rescue => e
326
- MessageBus.logger.warn "failed to process message #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
391
+ MessageBus.logger.warn "failed to deliver message, skipping #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
327
392
  end
328
-
329
393
  end
394
+ rescue => e
395
+ MessageBus.logger.warn "failed to process message #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
330
396
  end
331
397
  end
332
398
  end
@@ -341,6 +407,7 @@ end
341
407
 
342
408
  module MessageBus
343
409
  extend MessageBus::Implementation
410
+ initialize
344
411
  end
345
412
 
346
413
  # allows for multiple buses per app
@@ -6,6 +6,7 @@ class MessageBus::Client
6
6
  self.group_ids = opts[:group_ids] || []
7
7
  self.site_id = opts[:site_id]
8
8
  self.connect_time = Time.now
9
+ @bus = opts[:message_bus] || MessageBus
9
10
  @subscriptions = {}
10
11
  end
11
12
 
@@ -33,7 +34,7 @@ class MessageBus::Client
33
34
 
34
35
  def subscribe(channel, last_seen_id)
35
36
  last_seen_id = nil if last_seen_id == ""
36
- last_seen_id ||= MessageBus.last_id(channel)
37
+ last_seen_id ||= @bus.last_id(channel)
37
38
  @subscriptions[channel] = last_seen_id.to_i
38
39
  end
39
40
 
@@ -61,7 +62,7 @@ class MessageBus::Client
61
62
  end
62
63
 
63
64
  def filter(msg)
64
- filter = MessageBus.client_filter(msg.channel)
65
+ filter = @bus.client_filter(msg.channel)
65
66
 
66
67
  if filter
67
68
  filter.call(self.user_id, msg)
@@ -74,7 +75,7 @@ class MessageBus::Client
74
75
  r = []
75
76
  @subscriptions.each do |k,v|
76
77
  next if v.to_i < 0
77
- messages = MessageBus.backlog(k,v)
78
+ messages = @bus.backlog(k,v)
78
79
  messages.each do |msg|
79
80
  r << msg if allowed?(msg)
80
81
  end
@@ -84,7 +85,7 @@ class MessageBus::Client
84
85
  @subscriptions.each do |k,v|
85
86
  if v.to_i == -1
86
87
  status_message ||= {}
87
- status_message[k] = MessageBus.last_id(k)
88
+ status_message[k] = @bus.last_id(k)
88
89
  end
89
90
  end
90
91
  r << MessageBus::Message.new(-1, -1, '/__status', status_message) if status_message
@@ -2,9 +2,10 @@ require 'json' unless defined? ::JSON
2
2
 
3
3
  class MessageBus::ConnectionManager
4
4
 
5
- def initialize
5
+ def initialize(bus = nil)
6
6
  @clients = {}
7
7
  @subscriptions = {}
8
+ @bus = bus || MessageBus
8
9
  end
9
10
 
10
11
  def notify_clients(msg)
@@ -14,7 +15,7 @@ class MessageBus::ConnectionManager
14
15
 
15
16
  return unless subscription
16
17
 
17
- around_filter = MessageBus.around_client_batch(msg.channel)
18
+ around_filter = @bus.around_client_batch(msg.channel)
18
19
 
19
20
  work = lambda do
20
21
  subscription.each do |client_id|
@@ -3,10 +3,11 @@ module MessageBus::Rack; end
3
3
  class MessageBus::Rack::Diagnostics
4
4
  def initialize(app, config = {})
5
5
  @app = app
6
+ @bus = config[:message_bus] || MessageBus
6
7
  end
7
8
 
8
9
  def js_asset(name)
9
- return generate_script_tag(name) unless MessageBus.cache_assets
10
+ return generate_script_tag(name) unless @bus.cache_assets
10
11
  @@asset_cache ||= {}
11
12
  @@asset_cache[name] ||= generate_script_tag(name)
12
13
  @@asset_cache[name]
@@ -65,21 +66,21 @@ HTML
65
66
 
66
67
  route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
67
68
 
68
- if MessageBus.is_admin_lookup.nil? || !MessageBus.is_admin_lookup.call(env)
69
+ if @bus.is_admin_lookup.nil? || !@bus.is_admin_lookup.call(env)
69
70
  return [403, {}, ['not allowed']]
70
71
  end
71
72
 
72
73
  return index unless route
73
74
 
74
75
  if route == '/discover'
75
- user_id = MessageBus.user_id_lookup.call(env)
76
- MessageBus.publish('/_diagnostics/discover', user_id: user_id)
76
+ user_id = @bus.user_id_lookup.call(env)
77
+ @bus.publish('/_diagnostics/discover', user_id: user_id)
77
78
  return [200, {}, ['ok']]
78
79
  end
79
80
 
80
81
  if route =~ /^\/hup\//
81
82
  hostname, pid = route.split('/hup/')[1].split('/')
82
- MessageBus.publish('/_diagnostics/hup', {hostname: hostname, pid: pid.to_i})
83
+ @bus.publish('/_diagnostics/hup', {hostname: hostname, pid: pid.to_i})
83
84
  return [200, {}, ['ok']]
84
85
  end
85
86
 
@@ -3,19 +3,19 @@ module MessageBus::Rack; end
3
3
 
4
4
  class MessageBus::Rack::Middleware
5
5
 
6
- def self.start_listener
6
+ def start_listener
7
7
  unless @started_listener
8
8
 
9
9
  require 'eventmachine'
10
10
  require 'message_bus/em_ext'
11
11
 
12
- MessageBus.subscribe do |msg|
12
+ @subscription = @bus.subscribe do |msg|
13
13
  if EM.reactor_running?
14
14
  EM.next_tick do
15
15
  begin
16
- @@connection_manager.notify_clients(msg) if @@connection_manager
16
+ @connection_manager.notify_clients(msg) if @connection_manager
17
17
  rescue
18
- MessageBus.logger.warn "Failed to notify clients: #{$!} #{$!.backtrace}"
18
+ @bus.logger.warn "Failed to notify clients: #{$!} #{$!.backtrace}"
19
19
  end
20
20
  end
21
21
  end
@@ -26,8 +26,16 @@ class MessageBus::Rack::Middleware
26
26
 
27
27
  def initialize(app, config = {})
28
28
  @app = app
29
- @@connection_manager = MessageBus::ConnectionManager.new
30
- self.class.start_listener
29
+ @bus = config[:message_bus] || MessageBus
30
+ @connection_manager = MessageBus::ConnectionManager.new(@bus)
31
+ self.start_listener
32
+ end
33
+
34
+ def stop_listener
35
+ if @subscription
36
+ @bus.unsubscribe(&@subscription)
37
+ @started_listener = false
38
+ end
31
39
  end
32
40
 
33
41
  def self.backlog_to_json(backlog)
@@ -47,25 +55,25 @@ class MessageBus::Rack::Middleware
47
55
  return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus\//
48
56
 
49
57
  # special debug/test route
50
- if ::MessageBus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'.freeze
58
+ if @bus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'.freeze
51
59
  parsed = Rack::Request.new(env)
52
- ::MessageBus.publish parsed["channel".freeze], parsed["data".freeze]
60
+ @bus.publish parsed["channel".freeze], parsed["data".freeze]
53
61
  return [200,{"Content-Type".freeze => "text/html".freeze},["sent"]]
54
62
  end
55
63
 
56
64
  if env['PATH_INFO'].start_with? '/message-bus/_diagnostics'.freeze
57
- diags = MessageBus::Rack::Diagnostics.new(@app)
65
+ diags = MessageBus::Rack::Diagnostics.new(@app, message_bus: @bus)
58
66
  return diags.call(env)
59
67
  end
60
68
 
61
69
  client_id = env['PATH_INFO'].split("/")[2]
62
70
  return [404, {}, ["not found"]] unless client_id
63
71
 
64
- user_id = MessageBus.user_id_lookup.call(env) if MessageBus.user_id_lookup
65
- group_ids = MessageBus.group_ids_lookup.call(env) if MessageBus.group_ids_lookup
66
- site_id = MessageBus.site_id_lookup.call(env) if MessageBus.site_id_lookup
72
+ user_id = @bus.user_id_lookup.call(env) if @bus.user_id_lookup
73
+ group_ids = @bus.group_ids_lookup.call(env) if @bus.group_ids_lookup
74
+ site_id = @bus.site_id_lookup.call(env) if @bus.site_id_lookup
67
75
 
68
- client = MessageBus::Client.new(client_id: client_id, user_id: user_id, site_id: site_id, group_ids: group_ids)
76
+ client = MessageBus::Client.new(message_bus: @bus, client_id: client_id, user_id: user_id, site_id: site_id, group_ids: group_ids)
69
77
 
70
78
  request = Rack::Request.new(env)
71
79
  request.POST.each do |k,v|
@@ -79,22 +87,20 @@ class MessageBus::Rack::Middleware
79
87
 
80
88
  ensure_reactor
81
89
 
82
- long_polling = MessageBus.long_polling_enabled? &&
90
+ long_polling = @bus.long_polling_enabled? &&
83
91
  env['QUERY_STRING'] !~ /dlp=t/.freeze &&
84
92
  EM.reactor_running? &&
85
- @@connection_manager.client_count < MessageBus.max_active_clients
93
+ @connection_manager.client_count < @bus.max_active_clients
86
94
 
87
- #STDERR.puts "LONG POLLING lp enabled #{MessageBus.long_polling_enabled?}, reactor #{EM.reactor_running?} count: #{@@connection_manager.client_count} , active #{MessageBus.max_active_clients} #{long_polling}"
88
95
  if backlog.length > 0
89
96
  [200, headers, [self.class.backlog_to_json(backlog)] ]
90
- elsif long_polling && env['rack.hijack'] && MessageBus.rack_hijack_enabled?
97
+ elsif long_polling && env['rack.hijack'] && @bus.rack_hijack_enabled?
91
98
  io = env['rack.hijack'].call
92
99
  client.io = io
93
100
 
94
101
  add_client_with_timeout(client)
95
102
  [418, {}, ["I'm a teapot, undefined in spec"]]
96
103
  elsif long_polling && env['async.callback']
97
-
98
104
  response = nil
99
105
  # load extension if needed
100
106
  begin
@@ -122,19 +128,24 @@ class MessageBus::Rack::Middleware
122
128
  # ensure reactor is running
123
129
  if EM.reactor_pid != Process.pid
124
130
  Thread.new { EM.run }
131
+ i = 100
132
+ while !EM.reactor_running? && i > 0
133
+ sleep 0.001
134
+ i -= 1
135
+ end
125
136
  end
126
137
  end
127
138
 
128
139
  def add_client_with_timeout(client)
129
- @@connection_manager.add_client(client)
140
+ @connection_manager.add_client(client)
130
141
 
131
- client.cleanup_timer = ::EM::Timer.new(MessageBus.long_polling_interval.to_f / 1000) {
142
+ client.cleanup_timer = ::EM::Timer.new( @bus.long_polling_interval.to_f / 1000) {
132
143
  begin
133
144
  client.cleanup_timer = nil
134
145
  client.ensure_closed!
135
- @@connection_manager.remove_client(client)
146
+ @connection_manager.remove_client(client)
136
147
  rescue
137
- MessageBus.logger.warn "Failed to clean up client properly: #{$!} #{$!.backtrace}"
148
+ @bus.logger.warn "Failed to clean up client properly: #{$!} #{$!.backtrace}"
138
149
  end
139
150
  }
140
151
  end
@@ -9,6 +9,7 @@ require 'redis'
9
9
 
10
10
 
11
11
  class MessageBus::ReliablePubSub
12
+ attr_reader :subscribed
12
13
 
13
14
  UNSUB_MESSAGE = "$$UNSUBSCRIBE"
14
15
 
@@ -212,7 +213,6 @@ class MessageBus::ReliablePubSub
212
213
  end
213
214
 
214
215
  def global_unsubscribe
215
- # TODO mutex
216
216
  if @redis_global
217
217
  pub_redis.publish(redis_channel_name, UNSUB_MESSAGE)
218
218
  @redis_global.disconnect
@@ -249,7 +249,13 @@ class MessageBus::ReliablePubSub
249
249
  if highest_id
250
250
  clear_backlog.call(&blk)
251
251
  end
252
+ @subscribed = true
252
253
  end
254
+
255
+ on.unsubscribe do
256
+ @subscribed = false
257
+ end
258
+
253
259
  on.message do |c,m|
254
260
  if m == UNSUB_MESSAGE
255
261
  @redis_global.unsubscribe
@@ -1,3 +1,3 @@
1
1
  module MessageBus
2
- VERSION = "0.9.3.2"
2
+ VERSION = "0.9.4"
3
3
  end
@@ -23,7 +23,8 @@ end
23
23
  describe MessageBus::ConnectionManager do
24
24
 
25
25
  before do
26
- @manager = MessageBus::ConnectionManager.new
26
+ @bus = MessageBus
27
+ @manager = MessageBus::ConnectionManager.new(@bus)
27
28
  @client = MessageBus::Client.new(client_id: "xyz", user_id: 1, site_id: 10)
28
29
  @resp = FakeAsync.new
29
30
  @client.async_response = @resp
@@ -1,38 +1,47 @@
1
1
  require 'http/parser'
2
2
  class FakeAsyncMiddleware
3
3
 
4
- def self.simulate_thin_async
5
- @@simulate_thin_async = true
6
- @@simulate_hijack = false
4
+ def initialize(app,config={})
5
+ @app = app
6
+ @bus = config[:message_bus] || MessageBus
7
+ @simulate_thin_async = false
8
+ @simulate_hijack = false
9
+ @in_async = false
7
10
  end
8
11
 
9
- def self.simulate_hijack
10
- @@simulate_thin_async = false
11
- @@simulate_hijack = true
12
+ def app
13
+ @app
12
14
  end
13
15
 
14
- def self.in_async?
15
- @@in_async if defined? @@in_async
16
+ def simulate_thin_async
17
+ @simulate_thin_async = true
18
+ @simulate_hijack = false
16
19
  end
17
20
 
18
- def initialize(app,config={})
19
- @app = app
21
+ def simulate_hijack
22
+ @simulate_thin_async = false
23
+ @simulate_hijack = true
20
24
  end
21
25
 
22
- def simulate_thin_async
23
- @@simulate_thin_async && MessageBus.long_polling_enabled?
26
+ def in_async?
27
+ @in_async
24
28
  end
25
29
 
26
- def simulate_hijack
27
- @@simulate_hijack && MessageBus.long_polling_enabled?
30
+
31
+ def simulate_thin_async?
32
+ @simulate_thin_async && @bus.long_polling_enabled?
33
+ end
34
+
35
+ def simulate_hijack?
36
+ @simulate_hijack && @bus.long_polling_enabled?
28
37
  end
29
38
 
30
39
  def call(env)
31
- if simulate_thin_async
40
+ if simulate_thin_async?
32
41
  call_thin_async(env)
33
- elsif simulate_hijack
42
+ elsif simulate_hijack?
34
43
  call_rack_hijack(env)
35
- else
44
+ else
36
45
  @app.call(env)
37
46
  end
38
47
  end
@@ -64,12 +73,12 @@ class FakeAsyncMiddleware
64
73
  env['rack.hijack_io'] = io
65
74
 
66
75
  result = @app.call(env)
67
-
76
+
68
77
  EM::Timer.new(1) { EM.stop }
69
78
 
70
79
  defer = lambda {
71
80
  if !io || !io.closed?
72
- @@in_async = true
81
+ @in_async = true
73
82
  EM.next_tick do
74
83
  defer.call
75
84
  end
@@ -88,7 +97,7 @@ class FakeAsyncMiddleware
88
97
  end
89
98
  }
90
99
 
91
- @@in_async = false
100
+ @in_async = false
92
101
  result || [500, {}, ['timeout']]
93
102
 
94
103
  end
@@ -116,7 +125,7 @@ class FakeAsyncMiddleware
116
125
 
117
126
  defer = lambda {
118
127
  if !result
119
- @@in_async = true
128
+ @in_async = true
120
129
  EM.next_tick do
121
130
  defer.call
122
131
  end
@@ -127,7 +136,7 @@ class FakeAsyncMiddleware
127
136
  defer.call
128
137
  }
129
138
 
130
- @@in_async = false
139
+ @in_async = false
131
140
  result || [500, {}, ['timeout']]
132
141
  end
133
142
  end
@@ -14,6 +14,7 @@ describe MessageBus do
14
14
  end
15
15
 
16
16
  after do
17
+ @bus.reset!
17
18
  @bus.destroy
18
19
  end
19
20
 
@@ -79,6 +80,65 @@ describe MessageBus do
79
80
  r.map{|i| i.data}.to_a.should == ['foo', 'bar']
80
81
  end
81
82
 
83
+ it "allows you to look up last_message" do
84
+ @bus.publish("/bob", "dylan")
85
+ @bus.publish("/bob", "marley")
86
+ @bus.last_message("/bob").data.should == "marley"
87
+ @bus.last_message("/nothing").should == nil
88
+ end
89
+
90
+ context "global subscriptions" do
91
+ before do
92
+ seq = 0
93
+ @bus.site_id_lookup do
94
+ (seq+=1).to_s
95
+ end
96
+ end
97
+
98
+ it "can get last_message" do
99
+ @bus.publish("/global/test", "test")
100
+ @bus.last_message("/global/test").data.should == "test"
101
+ end
102
+
103
+ it "can subscribe globally" do
104
+
105
+ data = nil
106
+ @bus.subscribe do |message|
107
+ data = message.data
108
+ end
109
+
110
+ @bus.publish("/global/test", "test")
111
+ wait_for(1000){ data }
112
+
113
+ data.should == "test"
114
+ end
115
+
116
+ it "can subscribe to channel" do
117
+
118
+ data = nil
119
+ @bus.subscribe("/global/test") do |message|
120
+ data = message.data
121
+ end
122
+
123
+ @bus.publish("/global/test", "test")
124
+ wait_for(1000){ data }
125
+
126
+ data.should == "test"
127
+ end
128
+
129
+ it "should exception if publishing restricted messages to user" do
130
+ lambda do
131
+ @bus.publish("/global/test", "test", user_ids: [1])
132
+ end.should raise_error(MessageBus::InvalidMessage)
133
+ end
134
+
135
+ it "should exception if publishing restricted messages to group" do
136
+ lambda do
137
+ @bus.publish("/global/test", "test", user_ids: [1])
138
+ end.should raise_error(MessageBus::InvalidMessage)
139
+ end
140
+
141
+ end
82
142
 
83
143
  it "should support forking properly do" do
84
144
  data = nil
@@ -8,25 +8,37 @@ describe MessageBus::Rack::Middleware do
8
8
  include Rack::Test::Methods
9
9
 
10
10
  before do
11
- MessageBus.long_polling_enabled = false
11
+ bus = @bus = MessageBus::Instance.new
12
+ @bus.long_polling_enabled = false
13
+
14
+ builder = Rack::Builder.new {
15
+ use FakeAsyncMiddleware, :message_bus => bus
16
+ use MessageBus::Rack::Middleware, :message_bus => bus
17
+ run lambda {|env| [500, {'Content-Type' => 'text/html'}, 'should not be called' ]}
18
+ }
19
+
20
+ @async_middleware = builder.to_app
21
+ @message_bus_middleware = @async_middleware.app
22
+ end
23
+
24
+ after do |x|
25
+ @message_bus_middleware.stop_listener
26
+ @bus.reset!
27
+ @bus.destroy
12
28
  end
13
29
 
14
30
  def app
15
- @app ||= Rack::Builder.new {
16
- use FakeAsyncMiddleware
17
- use MessageBus::Rack::Middleware
18
- run lambda {|env| [500, {'Content-Type' => 'text/html'}, 'should not be called' ]}
19
- }.to_app
31
+ @async_middleware
20
32
  end
21
33
 
22
34
  shared_examples "long polling" do
23
35
  before do
24
- MessageBus.long_polling_enabled = true
36
+ @bus.long_polling_enabled = true
25
37
  end
26
38
 
27
39
  it "should respond right away if dlp=t" do
28
40
  post "/message-bus/ABC?dlp=t", '/foo1' => 0
29
- FakeAsyncMiddleware.in_async?.should == false
41
+ @async_middleware.in_async?.should == false
30
42
  last_response.should be_ok
31
43
  end
32
44
 
@@ -36,14 +48,16 @@ describe MessageBus::Rack::Middleware do
36
48
  parsed = JSON.parse(last_response.body)
37
49
  parsed.length.should == 1
38
50
  parsed[0]["channel"].should == "/__status"
39
- parsed[0]["data"]["/foo"].should == MessageBus.last_id("/foo")
51
+ parsed[0]["data"]["/foo"].should == @bus.last_id("/foo")
40
52
  end
41
53
 
42
54
  it "should respond to long polls when data is available" do
55
+ middleware = @async_middleware
56
+ bus = @bus
43
57
 
44
58
  Thread.new do
45
- wait_for(2000) { FakeAsyncMiddleware.in_async? }
46
- MessageBus.publish "/foo", "םוֹלשָׁ"
59
+ wait_for(2000) {middleware.in_async?}
60
+ bus.publish "/foo", "םוֹלשָׁ"
47
61
  end
48
62
 
49
63
  post "/message-bus/ABC", '/foo' => nil
@@ -56,21 +70,24 @@ describe MessageBus::Rack::Middleware do
56
70
 
57
71
  it "should timeout within its alloted slot" do
58
72
  begin
59
- MessageBus.long_polling_interval = 10
73
+ @bus.long_polling_interval = 10
60
74
  s = Time.now.to_f * 1000
61
75
  post "/message-bus/ABC", '/foo' => nil
62
76
  (Time.now.to_f * 1000 - s).should < 30
63
77
  ensure
64
- MessageBus.long_polling_interval = 5000
78
+ @bus.long_polling_interval = 5000
65
79
  end
66
80
  end
67
81
 
68
82
  it "should support batch filtering" do
69
- MessageBus.user_id_lookup do |env|
83
+ bus = @bus
84
+ async_middleware = @async_middleware
85
+
86
+ bus.user_id_lookup do |env|
70
87
  1
71
88
  end
72
89
 
73
- MessageBus.around_client_batch("/demo") do |message, user_ids, callback|
90
+ bus.around_client_batch("/demo") do |message, user_ids, callback|
74
91
  begin
75
92
  Thread.current["test"] = user_ids
76
93
  callback.call
@@ -81,18 +98,18 @@ describe MessageBus::Rack::Middleware do
81
98
 
82
99
  test = nil
83
100
 
84
- MessageBus.client_filter("/demo") do |user_id, message|
101
+ bus.client_filter("/demo") do |user_id, message|
85
102
  test = Thread.current["test"]
86
103
  message
87
104
  end
88
105
 
89
106
  client_id = "ABCD"
90
107
 
91
- id = MessageBus.publish("/demo", "test")
108
+ id = bus.publish("/demo", "test")
92
109
 
93
110
  Thread.new do
94
- wait_for(2000) { FakeAsyncMiddleware.in_async? }
95
- MessageBus.publish "/demo", "test"
111
+ wait_for(2000) { async_middleware.in_async? }
112
+ bus.publish "/demo", "test"
96
113
  end
97
114
 
98
115
  post "/message-bus/#{client_id}", {
@@ -105,15 +122,15 @@ describe MessageBus::Rack::Middleware do
105
122
 
106
123
  describe "thin async" do
107
124
  before do
108
- FakeAsyncMiddleware.simulate_thin_async
125
+ @async_middleware.simulate_thin_async
109
126
  end
110
127
  it_behaves_like "long polling"
111
128
  end
112
129
 
113
130
  describe "hijack" do
114
131
  before do
115
- FakeAsyncMiddleware.simulate_hijack
116
- MessageBus.rack_hijack_enabled = true
132
+ @async_middleware.simulate_hijack
133
+ @bus.rack_hijack_enabled = true
117
134
  end
118
135
  it_behaves_like "long polling"
119
136
  end
@@ -126,13 +143,13 @@ describe MessageBus::Rack::Middleware do
126
143
  end
127
144
 
128
145
  it "should get a 200 with html for an authorized user" do
129
- MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true })
146
+ @bus.stub(:is_admin_lookup).and_return(lambda{|env| true })
130
147
  get "/message-bus/_diagnostics"
131
148
  last_response.status.should == 200
132
149
  end
133
150
 
134
151
  it "should get the script it asks for" do
135
- MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true })
152
+ @bus.stub(:is_admin_lookup).and_return(lambda{|env| true })
136
153
  get "/message-bus/_diagnostics/assets/message-bus.js"
137
154
  last_response.status.should == 200
138
155
  last_response.content_type.should == "text/javascript;"
@@ -142,7 +159,7 @@ describe MessageBus::Rack::Middleware do
142
159
 
143
160
  describe "polling" do
144
161
  before do
145
- MessageBus.long_polling_enabled = false
162
+ @bus.long_polling_enabled = false
146
163
  end
147
164
 
148
165
  it "should respond with a 200 to a subscribe" do
@@ -158,7 +175,7 @@ describe MessageBus::Rack::Middleware do
158
175
 
159
176
  it "should correctly understand that -1 means stuff from now onwards" do
160
177
 
161
- MessageBus.publish('foo', 'bar')
178
+ @bus.publish('foo', 'bar')
162
179
 
163
180
  post "/message-bus/ABCD", {
164
181
  '/foo' => -1
@@ -167,15 +184,15 @@ describe MessageBus::Rack::Middleware do
167
184
  parsed = JSON.parse(last_response.body)
168
185
  parsed.length.should == 1
169
186
  parsed[0]["channel"].should == "/__status"
170
- parsed[0]["data"]["/foo"].should == MessageBus.last_id("/foo")
187
+ parsed[0]["data"]["/foo"].should ==@bus.last_id("/foo")
171
188
 
172
189
  end
173
190
 
174
191
  it "should respond with the data if messages exist in the backlog" do
175
- id = MessageBus.last_id('/foo')
192
+ id =@bus.last_id('/foo')
176
193
 
177
- MessageBus.publish("/foo", "barbs")
178
- MessageBus.publish("/foo", "borbs")
194
+ @bus.publish("/foo", "barbs")
195
+ @bus.publish("/foo", "borbs")
179
196
 
180
197
  client_id = "ABCD"
181
198
  post "/message-bus/#{client_id}", {
@@ -189,9 +206,46 @@ describe MessageBus::Rack::Middleware do
189
206
  parsed[1]["data"].should == "borbs"
190
207
  end
191
208
 
209
+ it "should have no cross talk" do
210
+
211
+ seq = 0
212
+ @bus.site_id_lookup do
213
+ (seq+=1).to_s
214
+ end
215
+
216
+ # published on channel 1
217
+ msg = @bus.publish("/foo", "test")
218
+
219
+ # subscribed on channel 2
220
+ post "/message-bus/ABCD", {
221
+ '/foo' => (msg-1)
222
+ }
223
+
224
+ parsed = JSON.parse(last_response.body)
225
+ parsed.length.should == 0
226
+
227
+ end
228
+
229
+ it "should have global cross talk" do
230
+
231
+ seq = 0
232
+ @bus.site_id_lookup do
233
+ (seq+=1).to_s
234
+ end
235
+
236
+ msg = @bus.publish("/global/foo", "test")
237
+
238
+ post "/message-bus/ABCD", {
239
+ '/global/foo' => (msg-1)
240
+ }
241
+
242
+ parsed = JSON.parse(last_response.body)
243
+ parsed.length.should == 1
244
+ end
245
+
192
246
  it "should not get consumed messages" do
193
- MessageBus.publish("/foo", "barbs")
194
- id = MessageBus.last_id('/foo')
247
+ @bus.publish("/foo", "barbs")
248
+ id =@bus.last_id('/foo')
195
249
 
196
250
  client_id = "ABCD"
197
251
  post "/message-bus/#{client_id}", {
@@ -203,8 +257,8 @@ describe MessageBus::Rack::Middleware do
203
257
  end
204
258
 
205
259
  it "should filter by user correctly" do
206
- id = MessageBus.publish("/foo", "test", user_ids: [1])
207
- MessageBus.user_id_lookup do |env|
260
+ id =@bus.publish("/foo", "test", user_ids: [1])
261
+ @bus.user_id_lookup do |env|
208
262
  0
209
263
  end
210
264
 
@@ -216,7 +270,7 @@ describe MessageBus::Rack::Middleware do
216
270
  parsed = JSON.parse(last_response.body)
217
271
  parsed.length.should == 0
218
272
 
219
- MessageBus.user_id_lookup do |env|
273
+ @bus.user_id_lookup do |env|
220
274
  1
221
275
  end
222
276
 
@@ -230,14 +284,14 @@ describe MessageBus::Rack::Middleware do
230
284
 
231
285
 
232
286
  it "should filter by client_filter correctly" do
233
- id = MessageBus.publish("/filter", "test")
234
- user_id = 0
287
+ id = @bus.publish("/filter", "test")
288
+ uid = 0
235
289
 
236
- MessageBus.user_id_lookup do |env|
237
- user_id
290
+ @bus.user_id_lookup do |env|
291
+ uid
238
292
  end
239
293
 
240
- MessageBus.client_filter("/filter") do |user_id, message|
294
+ @bus.client_filter("/filter") do |user_id, message|
241
295
  if user_id == 0
242
296
  message = message.dup
243
297
  message.data += "_filter"
@@ -256,7 +310,7 @@ describe MessageBus::Rack::Middleware do
256
310
  parsed = JSON.parse(last_response.body)
257
311
  parsed[0]['data'].should == "test_filter"
258
312
 
259
- user_id = 1
313
+ uid = 1
260
314
 
261
315
  post "/message-bus/#{client_id}", {
262
316
  '/filter' => id - 1
@@ -266,7 +320,7 @@ describe MessageBus::Rack::Middleware do
266
320
  parsed.length.should == 1
267
321
  parsed[0]["data"].should == "test"
268
322
 
269
- user_id = 2
323
+ uid = 2
270
324
 
271
325
  post "/message-bus/#{client_id}", {
272
326
  '/filter' => id - 1
@@ -277,8 +331,8 @@ describe MessageBus::Rack::Middleware do
277
331
  end
278
332
 
279
333
  it "should filter by group correctly" do
280
- id = MessageBus.publish("/foo", "test", group_ids: [3,4,5])
281
- MessageBus.group_ids_lookup do |env|
334
+ id =@bus.publish("/foo", "test", group_ids: [3,4,5])
335
+ @bus.group_ids_lookup do |env|
282
336
  [0,1,2]
283
337
  end
284
338
 
@@ -290,7 +344,7 @@ describe MessageBus::Rack::Middleware do
290
344
  parsed = JSON.parse(last_response.body)
291
345
  parsed.length.should == 0
292
346
 
293
- MessageBus.group_ids_lookup do |env|
347
+ @bus.group_ids_lookup do |env|
294
348
  [1,7,4,100]
295
349
  end
296
350
 
@@ -119,6 +119,7 @@ window.MessageBus = (function() {
119
119
  },
120
120
  success: function(messages) {
121
121
  failCount = 0;
122
+ if (messages === null) return; // server unexpectedly closed connection
122
123
  $.each(messages,function(_,message) {
123
124
  gotData = true;
124
125
  $.each(callbacks, function(_,callback) {
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: 0.9.3.2
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-06 00:00:00.000000000 Z
11
+ date: 2014-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -80,6 +80,8 @@ files:
80
80
  - examples/bench/unicorn.conf.rb
81
81
  - examples/chat/chat.rb
82
82
  - examples/chat/config.ru
83
+ - examples/minimal/Gemfile
84
+ - examples/minimal/config.ru
83
85
  - lib/message_bus.rb
84
86
  - lib/message_bus/client.rb
85
87
  - lib/message_bus/connection_manager.rb