message_bus 1.0.16 → 1.1.0

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: f42e4714f39aecb865965bb91a089a5bcd7a8670
4
- data.tar.gz: ab929f4b40c6ea23d6f680392da942234ec02457
3
+ metadata.gz: 7add5c68e797a080452032f17f17930bd2d28092
4
+ data.tar.gz: 15b55cad8b5dc0599d92fb88c74a83271703f46a
5
5
  SHA512:
6
- metadata.gz: 04ea180a321eb637807bc14bf657706a1cc66073043f41d915788fe74e8fe7590d80d1dfaddcbea24fe67639b10ff436638c5d6e21636183b93976e1589060a9
7
- data.tar.gz: 92eac39422601a62401f9c2ff2386e1ce686194fce6ca67b88f9948bdddc0d60b001feba16a07cd503bad83ca0ebe1debb2253767243fc6dec6e500cb67226a7
6
+ metadata.gz: 3385147eb91410e0606e95d32183f017cf218b86d2c51f2cfed7fd8ec7b15394c64fa6b64bcc9e3862e61781030b7c8f4d8fbd640f4c3e698dc28e2dd784e3ed
7
+ data.tar.gz: afcd0a5014c1c1f9ae229565d7dc7574f76eb1eecf544066acbed7b47db9d134b656a4bbc772ecf7285928f0e74b16a8c2e0f26cffa31aa252b55a86120066ef
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 07-12-2015
2
+
3
+ - Version 1.1.0
4
+ - Fix: keep track of client sequence on server, abandon old subscribes
5
+ - Fix: rare concurrency issue when subscribing concurrently
6
+ - Fature: remove most jQuery dependency from message-bus.js
7
+
1
8
  09-07-2015
2
9
  - Version 1.0.16
3
10
  - Fix: correct edge cases around keepalive checks on bus
data/README.md CHANGED
@@ -110,6 +110,40 @@ MessageBus.redis_config = { url: "redis://:p4ssw0rd@10.0.1.1:6380/15" }
110
110
  ```
111
111
  The redis client message_bus uses is [redis-rb](https://github.com/redis/redis-rb), so you can visit it's repo to see what options you can configure.
112
112
 
113
+ ### Forking/threading app servers
114
+
115
+ If you're using a forking or threading app server and you're not getting immediate updates from published messages, you might need to reconnect Redis in your app server config:
116
+
117
+ #### Passenger
118
+ ```ruby
119
+ # Rails: config/application.rb or config.ru
120
+ if defined?(PhusionPassenger)
121
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
122
+ if forked
123
+ # We're in smart spawning mode.
124
+ MessageBus.after_fork
125
+ else
126
+ # We're in conservative spawning mode. We don't need to do anything.
127
+ end
128
+ end
129
+ end
130
+ ```
131
+
132
+ #### Puma
133
+ ```ruby
134
+ # path/to/your/config/puma.rb
135
+ on_worker_boot do
136
+ MessageBus.after_fork
137
+ end
138
+ ```
139
+
140
+ #### Unicorn
141
+ ```ruby
142
+ # path/to/your/config/unicorn.rb
143
+ after_fork do |server, worker|
144
+ MessageBus.after_fork
145
+ end
146
+ ```
113
147
 
114
148
  ## Similar projects
115
149
 
@@ -33,12 +33,17 @@ window.MessageBus = (function() {
33
33
 
34
34
  var hiddenProperty;
35
35
 
36
- $.each(["","webkit","ms","moz","ms"], function(index, prefix){
37
- var check = prefix + (prefix === "" ? "hidden" : "Hidden");
38
- if(document[check] !== undefined ){
39
- hiddenProperty = check;
36
+
37
+ (function(){
38
+ var prefixes = ["","webkit","ms","moz","ms"];
39
+ for(var i=0; i<prefixes.length; i++) {
40
+ var prefix = prefixes[i];
41
+ var check = prefix + (prefix === "" ? "hidden" : "Hidden");
42
+ if(document[check] !== undefined ){
43
+ hiddenProperty = check;
44
+ }
40
45
  }
41
- });
46
+ })();
42
47
 
43
48
  var isHidden = function() {
44
49
  if (hiddenProperty !== undefined){
@@ -60,9 +65,12 @@ window.MessageBus = (function() {
60
65
  var gotData = false;
61
66
  if (!messages) return false; // server unexpectedly closed connection
62
67
 
63
- $.each(messages,function(_,message) {
68
+
69
+ for (var i=0; i<messages.length; i++) {
70
+ var message = messages[i];
64
71
  gotData = true;
65
- $.each(callbacks, function(_,callback) {
72
+ for (var j=0; j<callbacks.length; j++) {
73
+ var callback = callbacks[j];
66
74
  if (callback.channel === message.channel) {
67
75
  callback.last_id = message.message_id;
68
76
  try {
@@ -79,8 +87,8 @@ window.MessageBus = (function() {
79
87
  callback.last_id = message.data[callback.channel];
80
88
  }
81
89
  }
82
- });
83
- });
90
+ }
91
+ }
84
92
 
85
93
  return gotData;
86
94
  };
@@ -90,6 +98,7 @@ window.MessageBus = (function() {
90
98
  var aborted = false;
91
99
  lastAjax = new Date();
92
100
  totalAjaxCalls += 1;
101
+ data.__seq = totalAjaxCalls;
93
102
 
94
103
  return me.ajax({
95
104
  url: me.baseUrl + "message-bus/" + me.clientId + "/poll?" + (!shouldLongPoll() || !me.enableLongPolling ? "dlp=t" : ""),
@@ -104,9 +113,9 @@ window.MessageBus = (function() {
104
113
  failCount = 0;
105
114
  if (paused) {
106
115
  if (messages) {
107
- $.each(messages, function(_,message) {
108
- later.push(messages);
109
- });
116
+ for (var i=0; i<messages.length; i++) {
117
+ later.push(messages[i]);
118
+ }
110
119
  }
111
120
  } else {
112
121
  gotData = processMessages(messages);
@@ -215,9 +224,9 @@ window.MessageBus = (function() {
215
224
  }
216
225
 
217
226
  data = {};
218
- $.each(callbacks, function(_,callback) {
219
- data[callback.channel] = callback.last_id;
220
- });
227
+ for (var i=0;i<callbacks.length;i++) {
228
+ data[callbacks[i].channel] = callbacks[i].last_id;
229
+ }
221
230
 
222
231
  me.longPoll = longPoller(poll,data);
223
232
  };
@@ -265,7 +274,12 @@ window.MessageBus = (function() {
265
274
  channel = channel.substr(0, channel.length - 1);
266
275
  glob = true;
267
276
  }
268
- callbacks = $.grep(callbacks,function(callback) {
277
+
278
+ var filtered = [];
279
+
280
+ for (var i=0; i<callbacks.length; i++) {
281
+
282
+ callback = callbacks[i];
269
283
  var keep;
270
284
 
271
285
  if (glob) {
@@ -278,8 +292,12 @@ window.MessageBus = (function() {
278
292
  keep = true;
279
293
  }
280
294
 
281
- return keep;
282
- });
295
+ if (keep) {
296
+ filtered.push(callback);
297
+ }
298
+ }
299
+
300
+ callbacks = filtered;
283
301
 
284
302
  if (me.longPoll) {
285
303
  return me.longPoll.abort();
@@ -1,18 +1,28 @@
1
1
  class MessageBus::Client
2
2
  attr_accessor :client_id, :user_id, :group_ids, :connect_time,
3
3
  :subscribed_sets, :site_id, :cleanup_timer,
4
- :async_response, :io, :headers
4
+ :async_response, :io, :headers, :seq
5
5
 
6
6
  def initialize(opts)
7
7
  self.client_id = opts[:client_id]
8
8
  self.user_id = opts[:user_id]
9
9
  self.group_ids = opts[:group_ids] || []
10
10
  self.site_id = opts[:site_id]
11
+ self.seq = opts[:seq].to_i
11
12
  self.connect_time = Time.now
12
13
  @bus = opts[:message_bus] || MessageBus
13
14
  @subscriptions = {}
14
15
  end
15
16
 
17
+ def cancel
18
+ if cleanup_timer
19
+ # concurrency may nil cleanup timer
20
+ cleanup_timer.cancel rescue nil
21
+ self.cleanup_timer = nil
22
+ end
23
+ ensure_closed!
24
+ end
25
+
16
26
  def in_async?
17
27
  @async_response || @io
18
28
  end
@@ -2,117 +2,124 @@ require 'json' unless defined? ::JSON
2
2
 
3
3
  class MessageBus::ConnectionManager
4
4
  require 'monitor'
5
+ include MonitorMixin
5
6
 
6
- class SynchronizedSet
7
- include MonitorMixin
8
-
9
- def initialize
10
- super
11
- @set = Set.new
12
- end
13
-
14
- def self.synchronize(methods)
15
- methods.each do |method|
16
- define_method method do |*args, &blk|
17
- synchronize do
18
- @set.send method,*args,&blk
19
- end
20
- end
21
- end
22
- end
23
-
24
- synchronize(Set.new.methods - Object.new.methods)
25
-
26
- end
27
-
28
- def initialize(bus = nil)
7
+ def initialize(bus=nil)
29
8
  @clients = {}
30
9
  @subscriptions = {}
31
10
  @bus = bus || MessageBus
11
+ mon_initialize
32
12
  end
33
13
 
34
14
  def notify_clients(msg)
35
- begin
36
- site_subs = @subscriptions[msg.site_id]
37
- subscription = site_subs[msg.channel] if site_subs
38
-
39
- return unless subscription
40
-
41
- around_filter = @bus.around_client_batch(msg.channel)
42
-
43
- work = lambda do
44
- subscription.each do |client_id|
45
- client = @clients[client_id]
46
- if client && client.allowed?(msg)
47
- if copy = client.filter(msg)
48
- begin
49
- client << copy
50
- rescue
51
- # pipe may be broken, move on
15
+ synchronize do
16
+ begin
17
+ site_subs = @subscriptions[msg.site_id]
18
+ subscription = site_subs[msg.channel] if site_subs
19
+
20
+ return unless subscription
21
+
22
+ around_filter = @bus.around_client_batch(msg.channel)
23
+
24
+ work = lambda do
25
+ subscription.each do |client_id|
26
+ client = @clients[client_id]
27
+ if client && client.allowed?(msg)
28
+ if copy = client.filter(msg)
29
+ begin
30
+ client << copy
31
+ rescue
32
+ # pipe may be broken, move on
33
+ end
34
+ # turns out you can delete from a set while itereating
35
+ remove_client(client)
52
36
  end
53
- # turns out you can delete from a set while itereating
54
- remove_client(client)
55
37
  end
56
38
  end
57
39
  end
58
- end
59
40
 
60
- if around_filter
61
- user_ids = subscription.map do |s|
62
- c = @clients[s]
63
- c && c.user_id
64
- end.compact
41
+ if around_filter
42
+ user_ids = subscription.map do |s|
43
+ c = @clients[s]
44
+ c && c.user_id
45
+ end.compact
65
46
 
66
- if user_ids && user_ids.length > 0
67
- around_filter.call(msg, user_ids, work)
47
+ if user_ids && user_ids.length > 0
48
+ around_filter.call(msg, user_ids, work)
49
+ end
50
+ else
51
+ work.call
68
52
  end
69
- else
70
- work.call
71
- end
72
53
 
73
- rescue => e
74
- MessageBus.logger.error "notify clients crash #{e} : #{e.backtrace}"
54
+ rescue => e
55
+ MessageBus.logger.error "notify clients crash #{e} : #{e.backtrace}"
56
+ end
75
57
  end
76
58
  end
77
59
 
78
60
  def add_client(client)
79
- @clients[client.client_id] = client
80
- @subscriptions[client.site_id] ||= {}
81
- client.subscriptions.each do |k,v|
82
- subscribe_client(client, k)
61
+ synchronize do
62
+ existing = @clients[client.client_id]
63
+ if existing && existing.seq > client.seq
64
+ client.cancel
65
+ else
66
+ if existing
67
+ remove_client(existing)
68
+ existing.cancel
69
+ end
70
+
71
+ @clients[client.client_id] = client
72
+ @subscriptions[client.site_id] ||= {}
73
+ client.subscriptions.each do |k,v|
74
+ subscribe_client(client, k)
75
+ end
76
+ end
83
77
  end
84
78
  end
85
79
 
86
80
  def remove_client(c)
87
- @clients.delete c.client_id
88
- @subscriptions[c.site_id].each do |k, set|
89
- set.delete c.client_id
81
+ synchronize do
82
+ @clients.delete c.client_id
83
+ @subscriptions[c.site_id].each do |k, set|
84
+ set.delete c.client_id
85
+ end
86
+ if c.cleanup_timer
87
+ # concurrency may cause this to fail
88
+ c.cleanup_timer.cancel rescue nil
89
+ end
90
90
  end
91
- c.cleanup_timer.cancel if c.cleanup_timer
92
91
  end
93
92
 
94
93
  def lookup_client(client_id)
95
- @clients[client_id]
94
+ synchronize do
95
+ @clients[client_id]
96
+ end
96
97
  end
97
98
 
98
99
  def subscribe_client(client,channel)
99
- set = @subscriptions[client.site_id][channel]
100
- unless set
101
- set = SynchronizedSet.new
102
- @subscriptions[client.site_id][channel] = set
100
+ synchronize do
101
+ set = @subscriptions[client.site_id][channel]
102
+ unless set
103
+ set = Set.new
104
+ @subscriptions[client.site_id][channel] = set
105
+ end
106
+ set << client.client_id
103
107
  end
104
- set << client.client_id
105
108
  end
106
109
 
107
110
  def client_count
108
- @clients.length
111
+ synchronize do
112
+ @clients.length
113
+ end
109
114
  end
110
115
 
111
116
  def stats
112
- {
113
- client_count: @clients.length,
114
- subscriptions: @subscriptions
115
- }
117
+ synchronize do
118
+ {
119
+ client_count: @clients.length,
120
+ subscriptions: @subscriptions
121
+ }
122
+ end
116
123
  end
117
124
 
118
125
  end
@@ -86,7 +86,11 @@ class MessageBus::Rack::Middleware
86
86
 
87
87
  request = Rack::Request.new(env)
88
88
  request.POST.each do |k,v|
89
- client.subscribe(k, v)
89
+ if k == "__seq".freeze
90
+ client.seq = v.to_i
91
+ else
92
+ client.subscribe(k, v)
93
+ end
90
94
  end
91
95
 
92
96
  backlog = client.backlog
@@ -1,3 +1,3 @@
1
1
  module MessageBus
2
- VERSION = "1.0.16"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -84,6 +84,18 @@ end
84
84
 
85
85
  describe MessageBus::ConnectionManager, "notifying and subscribing concurrently" do
86
86
 
87
+ it "does not subscribe incorrect clients" do
88
+ manager = MessageBus::ConnectionManager.new
89
+
90
+ client1 = MessageBus::Client.new(client_id: "a", seq: 1)
91
+ client2 = MessageBus::Client.new(client_id: "a", seq: 2)
92
+
93
+ manager.add_client(client2)
94
+ manager.add_client(client1)
95
+
96
+ manager.lookup_client("a").should == client2
97
+ end
98
+
87
99
  it "is thread-safe" do
88
100
  @bus = MessageBus
89
101
  @manager = MessageBus::ConnectionManager.new(@bus)
@@ -33,12 +33,17 @@ window.MessageBus = (function() {
33
33
 
34
34
  var hiddenProperty;
35
35
 
36
- $.each(["","webkit","ms","moz","ms"], function(index, prefix){
37
- var check = prefix + (prefix === "" ? "hidden" : "Hidden");
38
- if(document[check] !== undefined ){
39
- hiddenProperty = check;
36
+
37
+ (function(){
38
+ var prefixes = ["","webkit","ms","moz","ms"];
39
+ for(var i=0; i<prefixes.length; i++) {
40
+ var prefix = prefixes[i];
41
+ var check = prefix + (prefix === "" ? "hidden" : "Hidden");
42
+ if(document[check] !== undefined ){
43
+ hiddenProperty = check;
44
+ }
40
45
  }
41
- });
46
+ })();
42
47
 
43
48
  var isHidden = function() {
44
49
  if (hiddenProperty !== undefined){
@@ -60,9 +65,12 @@ window.MessageBus = (function() {
60
65
  var gotData = false;
61
66
  if (!messages) return false; // server unexpectedly closed connection
62
67
 
63
- $.each(messages,function(_,message) {
68
+
69
+ for (var i=0; i<messages.length; i++) {
70
+ var message = messages[i];
64
71
  gotData = true;
65
- $.each(callbacks, function(_,callback) {
72
+ for (var j=0; j<callbacks.length; j++) {
73
+ var callback = callbacks[j];
66
74
  if (callback.channel === message.channel) {
67
75
  callback.last_id = message.message_id;
68
76
  try {
@@ -79,8 +87,8 @@ window.MessageBus = (function() {
79
87
  callback.last_id = message.data[callback.channel];
80
88
  }
81
89
  }
82
- });
83
- });
90
+ }
91
+ }
84
92
 
85
93
  return gotData;
86
94
  };
@@ -90,6 +98,7 @@ window.MessageBus = (function() {
90
98
  var aborted = false;
91
99
  lastAjax = new Date();
92
100
  totalAjaxCalls += 1;
101
+ data.__seq = totalAjaxCalls;
93
102
 
94
103
  return me.ajax({
95
104
  url: me.baseUrl + "message-bus/" + me.clientId + "/poll?" + (!shouldLongPoll() || !me.enableLongPolling ? "dlp=t" : ""),
@@ -104,9 +113,9 @@ window.MessageBus = (function() {
104
113
  failCount = 0;
105
114
  if (paused) {
106
115
  if (messages) {
107
- $.each(messages, function(_,message) {
108
- later.push(messages);
109
- });
116
+ for (var i=0; i<messages.length; i++) {
117
+ later.push(messages[i]);
118
+ }
110
119
  }
111
120
  } else {
112
121
  gotData = processMessages(messages);
@@ -215,9 +224,9 @@ window.MessageBus = (function() {
215
224
  }
216
225
 
217
226
  data = {};
218
- $.each(callbacks, function(_,callback) {
219
- data[callback.channel] = callback.last_id;
220
- });
227
+ for (var i=0;i<callbacks.length;i++) {
228
+ data[callbacks[i].channel] = callbacks[i].last_id;
229
+ }
221
230
 
222
231
  me.longPoll = longPoller(poll,data);
223
232
  };
@@ -265,7 +274,12 @@ window.MessageBus = (function() {
265
274
  channel = channel.substr(0, channel.length - 1);
266
275
  glob = true;
267
276
  }
268
- callbacks = $.grep(callbacks,function(callback) {
277
+
278
+ var filtered = [];
279
+
280
+ for (var i=0; i<callbacks.length; i++) {
281
+
282
+ callback = callbacks[i];
269
283
  var keep;
270
284
 
271
285
  if (glob) {
@@ -278,8 +292,12 @@ window.MessageBus = (function() {
278
292
  keep = true;
279
293
  }
280
294
 
281
- return keep;
282
- });
295
+ if (keep) {
296
+ filtered.push(callback);
297
+ }
298
+ }
299
+
300
+ callbacks = filtered;
283
301
 
284
302
  if (me.longPoll) {
285
303
  return me.longPoll.abort();
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: 1.0.16
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-09 00:00:00.000000000 Z
11
+ date: 2015-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -118,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  version: '0'
119
119
  requirements: []
120
120
  rubyforge_project:
121
- rubygems_version: 2.4.5
121
+ rubygems_version: 2.4.5.1
122
122
  signing_key:
123
123
  specification_version: 4
124
124
  summary: ''