message_bus 1.0.12 → 1.0.13
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/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
|