message_bus 0.9.3.2 → 0.9.4

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