message_bus 1.0.16 → 1.1.0

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