message_bus 1.0.12 → 1.0.13
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 +4 -4
- data/CHANGELOG +5 -0
- data/Gemfile +1 -0
- data/examples/chat/chat.rb +137 -4
- data/examples/chat/docker_container/chat.yml +5 -0
- data/lib/message_bus.rb +45 -2
- data/lib/message_bus/rack/middleware.rb +2 -9
- data/lib/message_bus/timer_thread.rb +29 -0
- data/lib/message_bus/version.rb +1 -1
- data/spec/lib/multi_process_spec.rb +1 -0
- data/spec/lib/timer_thread_spec.rb +27 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65597f9dd4a3b22117b594ccaff431aa8ed63198
|
4
|
+
data.tar.gz: 66c1a2a4079eb8d685e6029f1c0a6da4ecb7b7d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b5e51a5a51aec25039cef045b5c75cd4b27e610c7f96c7fa694aee4c7cc0a65b43be5224b8daed76f3a20908a8382baee9c20b15f281c5d76573c135599ea5e
|
7
|
+
data.tar.gz: 121e47af10c76c4ad0ffc7cd70f679632c9be4808adae2314ef977bafd82a341456feb5d18b2ac14827ee2abb4bf7723aad759df87ddab0330c054fd1787bfc2
|
data/CHANGELOG
CHANGED
data/Gemfile
CHANGED
data/examples/chat/chat.rb
CHANGED
@@ -2,7 +2,48 @@ $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
|
|
2
2
|
require 'message_bus'
|
3
3
|
require 'sinatra'
|
4
4
|
require 'sinatra/base'
|
5
|
+
require 'set'
|
6
|
+
require 'json'
|
5
7
|
|
8
|
+
$online = Hash.new
|
9
|
+
|
10
|
+
MessageBus.subscribe "/presence" do |msg|
|
11
|
+
if user = msg.data["enter"]
|
12
|
+
$online[user] = Time.now
|
13
|
+
end
|
14
|
+
if user = msg.data["leave"]
|
15
|
+
$online.delete user
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
MessageBus.user_id_lookup do |env|
|
20
|
+
name = env["HTTP_X_NAME"]
|
21
|
+
if name
|
22
|
+
unless $online[name]
|
23
|
+
MessageBus.publish "/presence", {enter: name}
|
24
|
+
end
|
25
|
+
$online[name] = Time.now
|
26
|
+
end
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
def expire_old_sessions
|
31
|
+
$online.each do |name,time|
|
32
|
+
if (Time.now - (5*60)) > time
|
33
|
+
puts "forcing leave for #{name} session timed out"
|
34
|
+
MessageBus.publish "/presence", {leave: name}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue => e
|
38
|
+
# need to make $online thread safe
|
39
|
+
p e
|
40
|
+
end
|
41
|
+
Thread.new do
|
42
|
+
while true
|
43
|
+
expire_old_sessions
|
44
|
+
sleep 1
|
45
|
+
end
|
46
|
+
end
|
6
47
|
|
7
48
|
class Chat < Sinatra::Base
|
8
49
|
|
@@ -10,8 +51,25 @@ class Chat < Sinatra::Base
|
|
10
51
|
|
11
52
|
use MessageBus::Rack::Middleware
|
12
53
|
|
54
|
+
post '/enter' do
|
55
|
+
name = params["name"]
|
56
|
+
i = 1
|
57
|
+
while $online.include? name
|
58
|
+
name = "#{params["name"]}#{i}"
|
59
|
+
i += 1
|
60
|
+
end
|
61
|
+
MessageBus.publish '/presence', {enter: name}
|
62
|
+
{users: $online.keys, name: name}.to_json
|
63
|
+
end
|
64
|
+
|
65
|
+
post '/leave' do
|
66
|
+
#puts "Got leave for #{params["name"]}"
|
67
|
+
MessageBus.publish '/presence', {leave: params["name"]}
|
68
|
+
end
|
69
|
+
|
13
70
|
post '/message' do
|
14
|
-
|
71
|
+
msg = {data: params["data"][0..500], name: params["name"][0..100]}
|
72
|
+
MessageBus.publish '/message', msg
|
15
73
|
|
16
74
|
"OK"
|
17
75
|
end
|
@@ -26,13 +84,29 @@ class Chat < Sinatra::Base
|
|
26
84
|
<script src="/message-bus.js"></script>
|
27
85
|
<style>
|
28
86
|
#panel { position: fixed; bottom: 0; background-color: #FFFFFF; }
|
87
|
+
#panel form { margin-bottom: 4px; }
|
88
|
+
#panel button, #panel textarea { height: 40px }
|
89
|
+
#panel button { width: 100px; vertical-align: top; }
|
29
90
|
#messages { padding-bottom: 40px; }
|
30
91
|
.hidden { display: none; }
|
92
|
+
#users {
|
93
|
+
position: fixed; top: 0; right: 0; width: 160px; background-color: #fafafa; height: 100%;
|
94
|
+
border-left: 1px solid #dfdfdf;
|
95
|
+
padding: 5px;
|
96
|
+
}
|
97
|
+
#users ul li { margin: 0; padding: 0; }
|
98
|
+
#users ul li.me { font-weight: bold; }
|
99
|
+
#users ul { list-style: none; margin 0; padding: 0; }
|
100
|
+
body { padding-right: 160px; }
|
31
101
|
</style>
|
32
102
|
</head>
|
33
103
|
<body>
|
34
104
|
<p>This is a trivial chat demo... It is implemented as a <a href="https://github.com/SamSaffron/message_bus/blob/master/examples/chat/chat.rb">Sinatra app</a>. The <a href="https://github.com/SamSaffron/message_bus">message_bus</a> can easily be added to any Rails/Rack app. <small>This app can be deployed with <a href="https://github.com/discourse/discourse_docker">Discourse Docker</a> using <a href="https://github.com/SamSaffron/message_bus/blob/master/examples/chat/docker_container/chat.yml">this template</a>.</small></p>
|
35
105
|
<div id='messages' class="hidden"></div>
|
106
|
+
<div id='users' class="hidden">
|
107
|
+
<h3>Online</h3>
|
108
|
+
<ul></ul>
|
109
|
+
</div>
|
36
110
|
<div id='panel' class="hidden">
|
37
111
|
<form>
|
38
112
|
<textarea cols=80 rows=2></textarea>
|
@@ -45,14 +119,67 @@ class Chat < Sinatra::Base
|
|
45
119
|
$(function() {
|
46
120
|
var name;
|
47
121
|
|
122
|
+
MessageBus.ajax = function(args){
|
123
|
+
args["headers"]["X-NAME"] = name;
|
124
|
+
return $.ajax(args);
|
125
|
+
};
|
126
|
+
|
127
|
+
var enter = function(name, opts) {
|
128
|
+
if (opts && opts.check) {
|
129
|
+
var found = false;
|
130
|
+
$('#users ul li').each(function(){
|
131
|
+
found = found || ($(this).text().trim() === name.trim());
|
132
|
+
if (found && opts.me) {
|
133
|
+
$(this).remove();
|
134
|
+
found = false;
|
135
|
+
}
|
136
|
+
return !found;
|
137
|
+
});
|
138
|
+
if (found) { return; }
|
139
|
+
}
|
140
|
+
|
141
|
+
var li = $('<li></li>');
|
142
|
+
li.text(name);
|
143
|
+
if (opts && opts.me) {
|
144
|
+
li.addClass("me");
|
145
|
+
$('#users ul').prepend(li);
|
146
|
+
} else {
|
147
|
+
$('#users ul').append(li);
|
148
|
+
}
|
149
|
+
};
|
150
|
+
|
48
151
|
$('#messages, #panel').addClass('hidden');
|
49
152
|
|
50
153
|
$('#your-name input').keyup(function(e){
|
51
154
|
if(e.keyCode == 13) {
|
52
155
|
name = $(this).val();
|
156
|
+
$.post("/enter", { name: name}, null, "json" ).success(function(data){
|
157
|
+
$.each(data.users,function(idx, name){
|
158
|
+
enter(name);
|
159
|
+
});
|
160
|
+
name = data.name;
|
161
|
+
enter(name, {check: true, me: true});
|
162
|
+
});
|
53
163
|
$('#your-name').addClass('hidden');
|
54
|
-
$('#messages, #panel').removeClass('hidden');
|
55
|
-
$(
|
164
|
+
$('#messages, #panel, #users').removeClass('hidden');
|
165
|
+
$(document.body).scrollTop(document.body.scrollHeight);
|
166
|
+
|
167
|
+
window.onbeforeunload = function(){
|
168
|
+
$.post("/leave", { name: name });
|
169
|
+
};
|
170
|
+
|
171
|
+
MessageBus.subscribe("/presence", function(msg){
|
172
|
+
if (msg.enter) {
|
173
|
+
enter(msg.enter, {check: true});
|
174
|
+
}
|
175
|
+
if (msg.leave) {
|
176
|
+
$('#users ul li').each(function(){
|
177
|
+
if ($(this).text() === msg.leave) {
|
178
|
+
$(this).remove()
|
179
|
+
}
|
180
|
+
});
|
181
|
+
}
|
182
|
+
});
|
56
183
|
}
|
57
184
|
});
|
58
185
|
|
@@ -70,7 +197,13 @@ class Chat < Sinatra::Base
|
|
70
197
|
if (val.length === 0) {
|
71
198
|
return;
|
72
199
|
}
|
73
|
-
|
200
|
+
|
201
|
+
if (val.length > 500) {
|
202
|
+
alert("message too long");
|
203
|
+
return false;
|
204
|
+
} else {
|
205
|
+
$.post("/message", { data: val, name: name} );
|
206
|
+
}
|
74
207
|
$('textarea').val("");
|
75
208
|
};
|
76
209
|
|
@@ -31,6 +31,11 @@ hooks:
|
|
31
31
|
- gem install unicorn
|
32
32
|
- gem install redis
|
33
33
|
- gem install sinatra
|
34
|
+
- gem install rbtrace
|
35
|
+
- replace:
|
36
|
+
- filename: $home/examples/chat/chat.rb
|
37
|
+
- from: "require 'json'"
|
38
|
+
- to: "require 'json'\nrequire 'rbtrace'"
|
34
39
|
- file:
|
35
40
|
path: /etc/service/unicorn/run
|
36
41
|
chmod: "+x"
|
data/lib/message_bus.rb
CHANGED
@@ -9,6 +9,7 @@ require "message_bus/diagnostics"
|
|
9
9
|
require "message_bus/rack/middleware"
|
10
10
|
require "message_bus/rack/diagnostics"
|
11
11
|
require "message_bus/redis/reliable_pub_sub"
|
12
|
+
require "message_bus/timer_thread"
|
12
13
|
|
13
14
|
# we still need to take care of the logger
|
14
15
|
if defined?(::Rails)
|
@@ -297,11 +298,14 @@ module MessageBus::Implementation
|
|
297
298
|
@destroyed = true
|
298
299
|
end
|
299
300
|
@subscriber_thread.join if @subscriber_thread
|
301
|
+
timer.stop
|
300
302
|
end
|
301
303
|
|
302
304
|
def after_fork
|
303
305
|
reliable_pub_sub.after_fork
|
304
306
|
ensure_subscriber_thread
|
307
|
+
# will ensure timer is running
|
308
|
+
timer.queue{}
|
305
309
|
end
|
306
310
|
|
307
311
|
def listening?
|
@@ -313,6 +317,17 @@ module MessageBus::Implementation
|
|
313
317
|
reliable_pub_sub.reset!
|
314
318
|
end
|
315
319
|
|
320
|
+
def timer
|
321
|
+
return @timer_thread if @timer_thread
|
322
|
+
@timer_thread ||= begin
|
323
|
+
t = MessageBus::TimerThread.new
|
324
|
+
t.on_error do |e|
|
325
|
+
logger.warn "Failed to process job: #{e} #{e.backtrace}"
|
326
|
+
end
|
327
|
+
t
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
316
331
|
protected
|
317
332
|
|
318
333
|
def global?(channel)
|
@@ -370,24 +385,51 @@ module MessageBus::Implementation
|
|
370
385
|
end
|
371
386
|
end
|
372
387
|
|
388
|
+
KEEPALIVE_INTERVAL = 60
|
389
|
+
|
373
390
|
def new_subscriber_thread
|
374
|
-
Thread.new do
|
391
|
+
thread = Thread.new do
|
375
392
|
begin
|
376
393
|
global_subscribe_thread unless @destroyed
|
377
394
|
rescue => e
|
378
395
|
MessageBus.logger.warn "Unexpected error in subscriber thread #{e}"
|
379
396
|
end
|
380
397
|
end
|
398
|
+
blk = proc do
|
399
|
+
if !@destroyed && thread.alive?
|
400
|
+
publish("/__mb_keepalive__/", Process.pid, user_ids: [-1])
|
401
|
+
if Time.now - (@last_message || Time.now) > KEEPALIVE_INTERVAL*2
|
402
|
+
MessageBus.logger.warn "Global messages on #{Process.pid} timed out, restarting thread"
|
403
|
+
# No other clean way to remove this thread, its listening on a socket
|
404
|
+
# no data is arriving
|
405
|
+
#
|
406
|
+
# In production we see this kind of situation ... sometimes ... when there is
|
407
|
+
# a VRRP failover, or weird networking condition
|
408
|
+
thread.kill
|
409
|
+
sleep 1
|
410
|
+
ensure_subscriber_thread
|
411
|
+
else
|
412
|
+
timer.queue(KEEPALIVE_INTERVAL, &blk)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
timer.queue(KEEPALIVE_INTERVAL, &blk)
|
417
|
+
|
418
|
+
thread
|
381
419
|
end
|
382
420
|
|
383
421
|
def global_subscribe_thread
|
384
|
-
|
422
|
+
# pretend we just got a message
|
423
|
+
@last_message = Time.now
|
424
|
+
reliable_pub_sub.global_subscribe(@global_id) do |msg|
|
385
425
|
begin
|
426
|
+
@last_message = Time.now
|
386
427
|
decode_message!(msg)
|
387
428
|
globals, locals, local_globals, global_globals = nil
|
388
429
|
|
389
430
|
@mutex.synchronize do
|
390
431
|
raise MessageBus::BusDestroyed if @destroyed
|
432
|
+
@last_message_time = Time.now
|
391
433
|
globals = @subscriptions[nil]
|
392
434
|
locals = @subscriptions[msg.site_id] if msg.site_id
|
393
435
|
|
@@ -408,6 +450,7 @@ module MessageBus::Implementation
|
|
408
450
|
rescue => e
|
409
451
|
MessageBus.logger.warn "failed to process message #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
|
410
452
|
end
|
453
|
+
@global_id = msg.global_id
|
411
454
|
end
|
412
455
|
end
|
413
456
|
|
@@ -5,12 +5,6 @@ class MessageBus::Rack::Middleware
|
|
5
5
|
|
6
6
|
def start_listener
|
7
7
|
unless @started_listener
|
8
|
-
require 'message_bus/timer_thread'
|
9
|
-
|
10
|
-
@timer_thread = MessageBus::TimerThread.new
|
11
|
-
@timer_thread.on_error do |e|
|
12
|
-
@bus.logger.warn "Failed to process job: #{e} #{e.backtrace}"
|
13
|
-
end
|
14
8
|
|
15
9
|
thin = defined?(Thin::Server) && ObjectSpace.each_object(Thin::Server).to_a.first
|
16
10
|
thin_running = thin && thin.running?
|
@@ -27,7 +21,7 @@ class MessageBus::Rack::Middleware
|
|
27
21
|
if thin_running
|
28
22
|
EM.next_tick(&run)
|
29
23
|
else
|
30
|
-
|
24
|
+
MessageBus.timer.queue(&run)
|
31
25
|
end
|
32
26
|
|
33
27
|
@started_listener = true
|
@@ -45,7 +39,6 @@ class MessageBus::Rack::Middleware
|
|
45
39
|
def stop_listener
|
46
40
|
if @subscription
|
47
41
|
@bus.unsubscribe(&@subscription)
|
48
|
-
@timer_thread.stop if @timer_thread
|
49
42
|
@started_listener = false
|
50
43
|
end
|
51
44
|
end
|
@@ -165,7 +158,7 @@ class MessageBus::Rack::Middleware
|
|
165
158
|
def add_client_with_timeout(client)
|
166
159
|
@connection_manager.add_client(client)
|
167
160
|
|
168
|
-
client.cleanup_timer =
|
161
|
+
client.cleanup_timer = MessageBus.timer.queue( @bus.long_polling_interval.to_f / 1000) {
|
169
162
|
begin
|
170
163
|
client.cleanup_timer = nil
|
171
164
|
client.ensure_closed!
|
@@ -13,6 +13,14 @@ class MessageBus::TimerThread
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
class CancelableEvery
|
17
|
+
attr_accessor :cancelled, :current
|
18
|
+
def cancel
|
19
|
+
current.cancel if current
|
20
|
+
@cancelled = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
16
24
|
def initialize
|
17
25
|
@stopped = false
|
18
26
|
@jobs = []
|
@@ -24,6 +32,27 @@ class MessageBus::TimerThread
|
|
24
32
|
|
25
33
|
def stop
|
26
34
|
@stopped = true
|
35
|
+
running = true
|
36
|
+
while running
|
37
|
+
@mutex.synchronize do
|
38
|
+
running = @thread && @thread.alive?
|
39
|
+
@thread.wakeup if running
|
40
|
+
end
|
41
|
+
sleep 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def every(delay, &block)
|
46
|
+
result = CancelableEvery.new
|
47
|
+
do_work = proc do
|
48
|
+
begin
|
49
|
+
block.call
|
50
|
+
ensure
|
51
|
+
result.current = queue(delay, &do_work)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
result.current = queue(delay,&do_work)
|
55
|
+
result
|
27
56
|
end
|
28
57
|
|
29
58
|
# queue a block to run after a certain delay (in seconds)
|
data/lib/message_bus/version.rb
CHANGED
@@ -10,6 +10,21 @@ describe MessageBus::TimerThread do
|
|
10
10
|
@timer.stop
|
11
11
|
end
|
12
12
|
|
13
|
+
it "allows you to queue every jobs" do
|
14
|
+
i = 0
|
15
|
+
m = Mutex.new
|
16
|
+
every = @timer.every(0.001){m.synchronize{i += 1}}
|
17
|
+
# allow lots of time, cause in test mode stuff can be slow
|
18
|
+
wait_for(1000) do
|
19
|
+
m.synchronize do
|
20
|
+
every.cancel if i == 3
|
21
|
+
i == 3
|
22
|
+
end
|
23
|
+
end
|
24
|
+
sleep 0.002
|
25
|
+
i.should == 3
|
26
|
+
end
|
27
|
+
|
13
28
|
it "allows you to cancel timers" do
|
14
29
|
success = true
|
15
30
|
@timer.queue(0.005){success=false}.cancel
|
@@ -21,18 +36,27 @@ describe MessageBus::TimerThread do
|
|
21
36
|
counter = 0
|
22
37
|
failed = nil
|
23
38
|
|
24
|
-
|
39
|
+
ready = 0
|
40
|
+
|
41
|
+
items = (0...4).to_a.shuffle
|
25
42
|
items.map do |i|
|
26
43
|
# threading introduces a delay meaning we need to wait a long time
|
27
44
|
Thread.new do
|
28
|
-
|
45
|
+
ready += 1
|
46
|
+
while ready < 4
|
47
|
+
sleep 0
|
48
|
+
end
|
49
|
+
# on my mbp I measure at least 200ms of schedule jitter for Thread
|
50
|
+
@timer.queue(i/3.0) do
|
51
|
+
#puts "counter #{counter} i #{i}"
|
29
52
|
failed = true if counter != i
|
30
53
|
counter += 1
|
31
54
|
end
|
55
|
+
#puts "\nqueued #{i/200.0} #{i} #{Time.now.to_f}\n"
|
32
56
|
end
|
33
57
|
end
|
34
58
|
|
35
|
-
wait_for(
|
59
|
+
wait_for(3000) {
|
36
60
|
counter == items.length
|
37
61
|
}
|
38
62
|
|
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.
|
4
|
+
version: 1.0.13
|
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-
|
11
|
+
date: 2015-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|