message_bus 2.0.2 → 2.0.3
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 +4 -4
- data/.rubocop.yml +104 -0
- data/CHANGELOG +9 -0
- data/README.md +62 -11
- data/lib/message_bus.rb +25 -25
- data/lib/message_bus/backends/memory.rb +14 -13
- data/lib/message_bus/backends/postgres.rb +34 -29
- data/lib/message_bus/backends/redis.rb +13 -15
- data/lib/message_bus/client.rb +21 -11
- data/lib/message_bus/connection_manager.rb +4 -3
- data/lib/message_bus/message.rb +7 -5
- data/lib/message_bus/rack/diagnostics.rb +5 -4
- data/lib/message_bus/rack/middleware.rb +19 -18
- data/lib/message_bus/rack/thin_ext.rb +2 -1
- data/lib/message_bus/rails/railtie.rb +16 -3
- data/lib/message_bus/timer_thread.rb +18 -14
- data/lib/message_bus/version.rb +2 -1
- data/message_bus.gemspec +2 -1
- data/spec/lib/message_bus/backends/postgres_spec.rb +1 -1
- data/spec/lib/message_bus/backends/redis_spec.rb +1 -1
- data/spec/lib/message_bus/client_spec.rb +24 -1
- data/spec/lib/message_bus/connection_manager_spec.rb +4 -4
- data/spec/lib/message_bus/rack/middleware_spec.rb +10 -2
- data/spec/lib/message_bus_spec.rb +1 -1
- data/vendor/assets/javascripts/message-bus-ajax.js +1 -0
- data/vendor/assets/javascripts/message-bus.js +1 -0
- metadata +14 -13
- data/vendor/assets/javascripts/message-bus-ajax.js +0 -44
- data/vendor/assets/javascripts/message-bus.js +0 -431
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'pg'
|
2
3
|
|
3
4
|
module MessageBus::Postgres; end
|
@@ -30,37 +31,41 @@ class MessageBus::Postgres::Client
|
|
30
31
|
@pid = Process.pid
|
31
32
|
end
|
32
33
|
|
33
|
-
def add(channel, value)
|
34
|
-
hold{|conn| exec_prepared(conn, 'insert_message', [channel, value]){|r| r.getvalue(0,0).to_i}}
|
34
|
+
def add (channel, value)
|
35
|
+
hold { |conn| exec_prepared(conn, 'insert_message', [channel, value]) { |r| r.getvalue(0, 0).to_i } }
|
35
36
|
end
|
36
37
|
|
37
38
|
def clear_global_backlog(backlog_id, num_to_keep)
|
38
39
|
if backlog_id > num_to_keep
|
39
|
-
hold{|conn| exec_prepared(conn, 'clear_global_backlog', [backlog_id - num_to_keep])}
|
40
|
+
hold { |conn| exec_prepared(conn, 'clear_global_backlog', [backlog_id - num_to_keep]) }
|
40
41
|
nil
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
45
|
def clear_channel_backlog(channel, backlog_id, num_to_keep)
|
45
|
-
hold{|conn| exec_prepared(conn, 'clear_channel_backlog', [channel, backlog_id, num_to_keep])}
|
46
|
+
hold { |conn| exec_prepared(conn, 'clear_channel_backlog', [channel, backlog_id, num_to_keep]) }
|
46
47
|
nil
|
47
48
|
end
|
48
49
|
|
49
50
|
def expire(max_backlog_age)
|
50
|
-
hold{|conn| exec_prepared(conn, 'expire', [max_backlog_age])}
|
51
|
+
hold { |conn| exec_prepared(conn, 'expire', [max_backlog_age]) }
|
51
52
|
nil
|
52
53
|
end
|
53
54
|
|
54
55
|
def backlog(channel, backlog_id)
|
55
|
-
hold
|
56
|
+
hold do |conn|
|
57
|
+
exec_prepared(conn, 'channel_backlog', [channel, backlog_id]) { |r| r.values.each { |a| a[0] = a[0].to_i } }
|
58
|
+
end || []
|
56
59
|
end
|
57
60
|
|
58
61
|
def global_backlog(backlog_id)
|
59
|
-
hold
|
62
|
+
hold do |conn|
|
63
|
+
exec_prepared(conn, 'global_backlog', [backlog_id]) { |r| r.values.each { |a| a[0] = a[0].to_i } }
|
64
|
+
end || []
|
60
65
|
end
|
61
66
|
|
62
67
|
def get_value(channel, id)
|
63
|
-
hold{|conn| exec_prepared(conn, 'get_message', [channel, id]){|r| r.getvalue(0,0)}}
|
68
|
+
hold { |conn| exec_prepared(conn, 'get_message', [channel, id]) { |r| r.getvalue(0, 0) } }
|
64
69
|
end
|
65
70
|
|
66
71
|
def reconnect
|
@@ -78,37 +83,37 @@ class MessageBus::Postgres::Client
|
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|
81
|
-
def max_id(channel=nil)
|
86
|
+
def max_id(channel = nil)
|
82
87
|
block = proc do |r|
|
83
88
|
if r.ntuples > 0
|
84
|
-
r.getvalue(0,0).to_i
|
89
|
+
r.getvalue(0, 0).to_i
|
85
90
|
else
|
86
91
|
0
|
87
92
|
end
|
88
93
|
end
|
89
94
|
|
90
95
|
if channel
|
91
|
-
hold{|conn| exec_prepared(conn, 'max_channel_id', [channel], &block)}
|
96
|
+
hold { |conn| exec_prepared(conn, 'max_channel_id', [channel], &block) }
|
92
97
|
else
|
93
|
-
hold{|conn| exec_prepared(conn, 'max_id', &block)}
|
98
|
+
hold { |conn| exec_prepared(conn, 'max_id', &block) }
|
94
99
|
end
|
95
100
|
end
|
96
101
|
|
97
102
|
def publish(channel, data)
|
98
|
-
hold{|conn| exec_prepared(conn, 'publish', [channel, data])}
|
103
|
+
hold { |conn| exec_prepared(conn, 'publish', [channel, data]) }
|
99
104
|
end
|
100
105
|
|
101
106
|
def subscribe(channel)
|
102
107
|
obj = Object.new
|
103
|
-
sync{@listening_on[channel] = obj}
|
108
|
+
sync { @listening_on[channel] = obj }
|
104
109
|
listener = Listener.new
|
105
110
|
yield listener
|
106
|
-
|
111
|
+
|
107
112
|
conn = raw_pg_connection
|
108
113
|
conn.exec "LISTEN #{channel}"
|
109
114
|
listener.do_sub.call
|
110
115
|
while listening_on?(channel, obj)
|
111
|
-
conn.wait_for_notify(10) do |_,_,payload|
|
116
|
+
conn.wait_for_notify(10) do |_, _, payload|
|
112
117
|
break unless listening_on?(channel, obj)
|
113
118
|
listener.do_message.call(nil, payload)
|
114
119
|
end
|
@@ -120,7 +125,7 @@ class MessageBus::Postgres::Client
|
|
120
125
|
end
|
121
126
|
|
122
127
|
def unsubscribe
|
123
|
-
sync{@listening_on.clear}
|
128
|
+
sync { @listening_on.clear }
|
124
129
|
end
|
125
130
|
|
126
131
|
private
|
@@ -149,22 +154,22 @@ class MessageBus::Postgres::Client
|
|
149
154
|
end
|
150
155
|
end
|
151
156
|
|
152
|
-
if conn = sync{@allocated[Thread.current]}
|
157
|
+
if conn = sync { @allocated[Thread.current] }
|
153
158
|
return yield(conn)
|
154
159
|
end
|
155
|
-
|
160
|
+
|
156
161
|
begin
|
157
|
-
conn = sync{@available.shift} || new_pg_connection
|
158
|
-
sync{@allocated[Thread.current] = conn}
|
162
|
+
conn = sync { @available.shift } || new_pg_connection
|
163
|
+
sync { @allocated[Thread.current] = conn }
|
159
164
|
yield conn
|
160
165
|
rescue PG::ConnectionBad, PG::UnableToSend => e
|
161
166
|
# don't add this connection back to the pool
|
162
167
|
ensure
|
163
|
-
sync{@allocated.delete(Thread.current)}
|
168
|
+
sync { @allocated.delete(Thread.current) }
|
164
169
|
if Process.pid != current_pid
|
165
|
-
sync{INHERITED_CONNECTIONS << conn}
|
170
|
+
sync { INHERITED_CONNECTIONS << conn }
|
166
171
|
elsif conn && !e
|
167
|
-
sync{@available << conn}
|
172
|
+
sync { @available << conn }
|
168
173
|
end
|
169
174
|
end
|
170
175
|
end
|
@@ -197,11 +202,11 @@ class MessageBus::Postgres::Client
|
|
197
202
|
end
|
198
203
|
|
199
204
|
def listening_on?(channel, obj)
|
200
|
-
sync{@listening_on[channel]} == obj
|
205
|
+
sync { @listening_on[channel] } == obj
|
201
206
|
end
|
202
207
|
|
203
208
|
def sync
|
204
|
-
@mutex.synchronize{yield}
|
209
|
+
@mutex.synchronize { yield }
|
205
210
|
end
|
206
211
|
end
|
207
212
|
|
@@ -251,7 +256,7 @@ class MessageBus::Postgres::ReliablePubSub
|
|
251
256
|
client.reset!
|
252
257
|
end
|
253
258
|
|
254
|
-
def publish(channel, data, queue_in_memory=true)
|
259
|
+
def publish(channel, data, queue_in_memory = true)
|
255
260
|
client = self.client
|
256
261
|
backlog_id = client.add(channel, data)
|
257
262
|
msg = MessageBus::Message.new backlog_id, backlog_id, channel, data
|
@@ -322,7 +327,7 @@ class MessageBus::Postgres::ReliablePubSub
|
|
322
327
|
@subscribed = false
|
323
328
|
end
|
324
329
|
|
325
|
-
def global_subscribe(last_id=nil, &blk)
|
330
|
+
def global_subscribe(last_id = nil, &blk)
|
326
331
|
raise ArgumentError unless block_given?
|
327
332
|
highest_id = last_id
|
328
333
|
|
@@ -345,7 +350,7 @@ class MessageBus::Postgres::ReliablePubSub
|
|
345
350
|
@subscribed = false
|
346
351
|
end
|
347
352
|
|
348
|
-
on.message do |c,m|
|
353
|
+
on.message do |c, m|
|
349
354
|
if m == UNSUB_MESSAGE
|
350
355
|
@subscribed = false
|
351
356
|
return
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'redis'
|
2
3
|
# the heart of the message bus, it acts as 2 things
|
3
4
|
#
|
@@ -80,7 +81,7 @@ class MessageBus::Redis::ReliablePubSub
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
83
|
-
def publish(channel, data, queue_in_memory=true)
|
84
|
+
def publish(channel, data, queue_in_memory = true)
|
84
85
|
redis = pub_redis
|
85
86
|
backlog_id_key = backlog_id_key(channel)
|
86
87
|
backlog_key = backlog_key(channel)
|
@@ -122,21 +123,19 @@ class MessageBus::Redis::ReliablePubSub
|
|
122
123
|
backlog_id
|
123
124
|
|
124
125
|
rescue Redis::CommandError => e
|
125
|
-
if queue_in_memory &&
|
126
|
-
e.message =~ /^READONLY/
|
127
|
-
|
126
|
+
if queue_in_memory && e.message =~ /^READONLY/
|
128
127
|
@lock.synchronize do
|
129
|
-
@in_memory_backlog << [channel,data]
|
128
|
+
@in_memory_backlog << [channel, data]
|
130
129
|
if @in_memory_backlog.length > @max_in_memory_publish_backlog
|
131
130
|
@in_memory_backlog.delete_at(0)
|
132
|
-
MessageBus.logger.warn("Dropping old message cause max_in_memory_publish_backlog is full")
|
131
|
+
MessageBus.logger.warn("Dropping old message cause max_in_memory_publish_backlog is full: #{e.message}\n#{e.backtrace.join('\n')}")
|
133
132
|
end
|
134
133
|
end
|
135
134
|
|
136
135
|
if @flush_backlog_thread == nil
|
137
136
|
@lock.synchronize do
|
138
137
|
if @flush_backlog_thread == nil
|
139
|
-
@flush_backlog_thread = Thread.new{ensure_backlog_flushed}
|
138
|
+
@flush_backlog_thread = Thread.new { ensure_backlog_flushed }
|
140
139
|
end
|
141
140
|
end
|
142
141
|
end
|
@@ -164,15 +163,15 @@ class MessageBus::Redis::ReliablePubSub
|
|
164
163
|
end
|
165
164
|
|
166
165
|
begin
|
167
|
-
publish(*@in_memory_backlog[0],false)
|
166
|
+
publish(*@in_memory_backlog[0], false)
|
168
167
|
rescue Redis::CommandError => e
|
169
168
|
if e.message =~ /^READONLY/
|
170
169
|
try_again = true
|
171
170
|
else
|
172
|
-
MessageBus.logger.warn("Dropping undeliverable message #{e}")
|
171
|
+
MessageBus.logger.warn("Dropping undeliverable message: #{e.message}\n#{e.backtrace.join('\n')}")
|
173
172
|
end
|
174
173
|
rescue => e
|
175
|
-
MessageBus.logger.warn("Dropping undeliverable message #{e}")
|
174
|
+
MessageBus.logger.warn("Dropping undeliverable message: #{e.message}\n#{e.backtrace.join('\n')}")
|
176
175
|
end
|
177
176
|
|
178
177
|
@in_memory_backlog.delete_at(0) unless try_again
|
@@ -208,7 +207,7 @@ class MessageBus::Redis::ReliablePubSub
|
|
208
207
|
items.map! do |i|
|
209
208
|
pipe = i.index "|"
|
210
209
|
message_id = i[0..pipe].to_i
|
211
|
-
channel = i[pipe+1..-1]
|
210
|
+
channel = i[pipe + 1..-1]
|
212
211
|
m = get_message(channel, message_id)
|
213
212
|
m
|
214
213
|
end
|
@@ -277,7 +276,7 @@ class MessageBus::Redis::ReliablePubSub
|
|
277
276
|
end
|
278
277
|
end
|
279
278
|
|
280
|
-
def global_subscribe(last_id=nil, &blk)
|
279
|
+
def global_subscribe(last_id = nil, &blk)
|
281
280
|
raise ArgumentError unless block_given?
|
282
281
|
highest_id = last_id
|
283
282
|
|
@@ -293,7 +292,6 @@ class MessageBus::Redis::ReliablePubSub
|
|
293
292
|
end
|
294
293
|
end
|
295
294
|
|
296
|
-
|
297
295
|
begin
|
298
296
|
@redis_global = new_redis_connection
|
299
297
|
|
@@ -313,7 +311,7 @@ class MessageBus::Redis::ReliablePubSub
|
|
313
311
|
@subscribed = false
|
314
312
|
end
|
315
313
|
|
316
|
-
on.message do |c,m|
|
314
|
+
on.message do |c, m|
|
317
315
|
if m == UNSUB_MESSAGE
|
318
316
|
@redis_global.unsubscribe
|
319
317
|
return
|
@@ -344,7 +342,7 @@ class MessageBus::Redis::ReliablePubSub
|
|
344
342
|
private
|
345
343
|
|
346
344
|
def is_readonly?
|
347
|
-
key = "__mb_is_readonly"
|
345
|
+
key = "__mb_is_readonly"
|
348
346
|
|
349
347
|
begin
|
350
348
|
# in case we are not connected to the correct server
|
data/lib/message_bus/client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class MessageBus::Client
|
2
3
|
attr_accessor :client_id, :user_id, :group_ids, :connect_time,
|
3
4
|
:subscribed_sets, :site_id, :cleanup_timer,
|
@@ -45,21 +46,21 @@ class MessageBus::Client
|
|
45
46
|
|
46
47
|
def ensure_first_chunk_sent
|
47
48
|
if use_chunked && @chunks_sent == 0
|
48
|
-
write_chunk("[]"
|
49
|
+
write_chunk("[]")
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
53
|
def ensure_closed!
|
53
54
|
return unless in_async?
|
54
55
|
if use_chunked
|
55
|
-
write_chunk("[]"
|
56
|
+
write_chunk("[]")
|
56
57
|
if @io
|
57
|
-
@io.write("0\r\n\r\n"
|
58
|
+
@io.write("0\r\n\r\n")
|
58
59
|
@io.close
|
59
60
|
@io = nil
|
60
61
|
end
|
61
62
|
if @async_response
|
62
|
-
@async_response << ("0\r\n\r\n"
|
63
|
+
@async_response << ("0\r\n\r\n")
|
63
64
|
@async_response.done
|
64
65
|
@async_response = nil
|
65
66
|
end
|
@@ -117,21 +118,31 @@ class MessageBus::Client
|
|
117
118
|
|
118
119
|
def backlog
|
119
120
|
r = []
|
120
|
-
|
121
|
+
new_message_ids = nil
|
122
|
+
|
123
|
+
@subscriptions.each do |k, v|
|
121
124
|
next if v.to_i < 0
|
122
125
|
messages = @bus.backlog(k, v, site_id)
|
126
|
+
|
123
127
|
messages.each do |msg|
|
124
|
-
|
128
|
+
if allowed?(msg)
|
129
|
+
r << msg
|
130
|
+
else
|
131
|
+
new_message_ids ||= {}
|
132
|
+
new_message_ids[k] = msg.message_id
|
133
|
+
end
|
125
134
|
end
|
126
135
|
end
|
136
|
+
|
127
137
|
# stats message for all newly subscribed
|
128
138
|
status_message = nil
|
129
|
-
@subscriptions.each do |k,v|
|
130
|
-
if v.to_i == -1
|
139
|
+
@subscriptions.each do |k, v|
|
140
|
+
if v.to_i == -1 || (new_message_ids && new_message_ids[k])
|
131
141
|
status_message ||= {}
|
132
142
|
@subscriptions[k] = status_message[k] = @bus.last_id(k, site_id)
|
133
143
|
end
|
134
144
|
end
|
145
|
+
|
135
146
|
r << MessageBus::Message.new(-1, -1, '/__status', status_message) if status_message
|
136
147
|
|
137
148
|
r || []
|
@@ -139,9 +150,8 @@ class MessageBus::Client
|
|
139
150
|
|
140
151
|
protected
|
141
152
|
|
142
|
-
|
143
153
|
# heavily optimised to avoid all uneeded allocations
|
144
|
-
NEWLINE="\r\n".freeze
|
154
|
+
NEWLINE = "\r\n".freeze
|
145
155
|
COLON_SPACE = ": ".freeze
|
146
156
|
HTTP_11 = "HTTP/1.1 200 OK\r\n".freeze
|
147
157
|
CONTENT_LENGTH = "Content-Length: ".freeze
|
@@ -154,7 +164,7 @@ class MessageBus::Client
|
|
154
164
|
|
155
165
|
def write_headers
|
156
166
|
@io.write(HTTP_11)
|
157
|
-
@headers.each do |k,v|
|
167
|
+
@headers.each do |k, v|
|
158
168
|
next if k == "Content-Type"
|
159
169
|
@io.write(k)
|
160
170
|
@io.write(COLON_SPACE)
|
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'json' unless defined? ::JSON
|
2
3
|
|
3
4
|
class MessageBus::ConnectionManager
|
4
5
|
require 'monitor'
|
5
6
|
include MonitorMixin
|
6
7
|
|
7
|
-
def initialize(bus=nil)
|
8
|
+
def initialize(bus = nil)
|
8
9
|
@clients = {}
|
9
10
|
@subscriptions = {}
|
10
11
|
@bus = bus || MessageBus
|
@@ -53,7 +54,7 @@ class MessageBus::ConnectionManager
|
|
53
54
|
|
54
55
|
@clients[client.client_id] = client
|
55
56
|
@subscriptions[client.site_id] ||= {}
|
56
|
-
client.subscriptions.each do |k,v|
|
57
|
+
client.subscriptions.each do |k, v|
|
57
58
|
subscribe_client(client, k)
|
58
59
|
end
|
59
60
|
end
|
@@ -79,7 +80,7 @@ class MessageBus::ConnectionManager
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
82
|
-
def subscribe_client(client,channel)
|
83
|
+
def subscribe_client(client, channel)
|
83
84
|
synchronize do
|
84
85
|
set = @subscriptions[client.site_id][channel]
|
85
86
|
unless set
|
data/lib/message_bus/message.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class MessageBus::Message < Struct.new(:global_id, :message_id, :channel , :data)
|
2
4
|
|
3
5
|
attr_accessor :site_id, :user_ids, :group_ids, :client_ids
|
4
6
|
|
5
7
|
def self.decode(encoded)
|
6
8
|
s1 = encoded.index("|")
|
7
|
-
s2 = encoded.index("|", s1+1)
|
8
|
-
s3 = encoded.index("|", s2+1)
|
9
|
+
s2 = encoded.index("|", s1 + 1)
|
10
|
+
s3 = encoded.index("|", s2 + 1)
|
9
11
|
|
10
|
-
MessageBus::Message.new(encoded[0..s1].to_i, encoded[s1+1..s2].to_i,
|
11
|
-
encoded[s2+1..s3-1].gsub("$$123$$", "|"), encoded[s3+1..-1])
|
12
|
+
MessageBus::Message.new(encoded[0..s1].to_i, encoded[(s1 + 1)..s2].to_i,
|
13
|
+
encoded[(s2 + 1)..(s3 - 1)].gsub("$$123$$", "|"), encoded[(s3 + 1)..-1])
|
12
14
|
end
|
13
15
|
|
14
16
|
# only tricky thing to encode is pipes in a channel name ... do a straight replace
|
15
17
|
def encode
|
16
|
-
global_id.to_s << "|" << message_id.to_s << "|" << channel.gsub("|","$$123$$") << "|" << data
|
18
|
+
global_id.to_s << "|" << message_id.to_s << "|" << channel.gsub("|", "$$123$$") << "|" << data
|
17
19
|
end
|
18
20
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module MessageBus::Rack; end
|
2
3
|
|
3
4
|
class MessageBus::Rack::Diagnostics
|
@@ -48,7 +49,7 @@ class MessageBus::Rack::Diagnostics
|
|
48
49
|
</body>
|
49
50
|
</html>
|
50
51
|
HTML
|
51
|
-
return [200, {"content-type" => "text/html;"}, [html]]
|
52
|
+
return [200, { "content-type" => "text/html;" }, [html]]
|
52
53
|
end
|
53
54
|
|
54
55
|
def translate_handlebars(name, content)
|
@@ -80,7 +81,7 @@ HTML
|
|
80
81
|
|
81
82
|
if route =~ /^\/hup\//
|
82
83
|
hostname, pid = route.split('/hup/')[1].split('/')
|
83
|
-
@bus.publish('/_diagnostics/hup',
|
84
|
+
@bus.publish('/_diagnostics/hup', hostname: hostname, pid: pid.to_i)
|
84
85
|
return [200, {}, ['ok']]
|
85
86
|
end
|
86
87
|
|
@@ -89,9 +90,9 @@ HTML
|
|
89
90
|
content = asset_contents(asset)
|
90
91
|
split = asset.split('.')
|
91
92
|
if split[1] == 'handlebars'
|
92
|
-
content = translate_handlebars(split[0],content)
|
93
|
+
content = translate_handlebars(split[0], content)
|
93
94
|
end
|
94
|
-
return [200, {'content-type' => 'text/javascript;'}, [content]]
|
95
|
+
return [200, { 'content-type' => 'text/javascript;' }, [content]]
|
95
96
|
end
|
96
97
|
|
97
98
|
return [404, {}, ['not found']]
|