message_bus 0.0.2 → 0.9.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.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG +9 -0
  5. data/Gemfile +15 -0
  6. data/Guardfile +7 -0
  7. data/README.md +8 -0
  8. data/Rakefile +14 -0
  9. data/assets/application.handlebars +7 -0
  10. data/assets/application.js +79 -0
  11. data/assets/ember.js +26839 -0
  12. data/assets/handlebars.js +2201 -0
  13. data/assets/index.handlebars +25 -0
  14. data/assets/jquery-1.8.2.js +9440 -0
  15. data/assets/message-bus.js +247 -0
  16. data/examples/bench/ab.sample +1 -0
  17. data/examples/bench/config.ru +24 -0
  18. data/examples/bench/payload.post +1 -0
  19. data/examples/bench/unicorn.conf.rb +4 -0
  20. data/examples/chat/chat.rb +74 -0
  21. data/examples/chat/config.ru +2 -0
  22. data/lib/message_bus.rb +60 -5
  23. data/lib/message_bus/client.rb +45 -7
  24. data/lib/message_bus/connection_manager.rb +35 -7
  25. data/lib/message_bus/em_ext.rb +5 -0
  26. data/lib/message_bus/rack/middleware.rb +60 -89
  27. data/lib/message_bus/rack/thin_ext.rb +71 -0
  28. data/lib/message_bus/rails/railtie.rb +4 -1
  29. data/lib/message_bus/reliable_pub_sub.rb +22 -4
  30. data/lib/message_bus/version.rb +1 -1
  31. data/message_bus.gemspec +20 -0
  32. data/spec/lib/client_spec.rb +50 -0
  33. data/spec/lib/connection_manager_spec.rb +83 -0
  34. data/spec/lib/fake_async_middleware.rb +134 -0
  35. data/spec/lib/handlers/demo_message_handler.rb +5 -0
  36. data/spec/lib/message_bus_spec.rb +112 -0
  37. data/spec/lib/message_handler_spec.rb +39 -0
  38. data/spec/lib/middleware_spec.rb +306 -0
  39. data/spec/lib/multi_process_spec.rb +60 -0
  40. data/spec/lib/reliable_pub_sub_spec.rb +167 -0
  41. data/spec/spec_helper.rb +19 -0
  42. data/vendor/assets/javascripts/message-bus.js +247 -0
  43. metadata +55 -26
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'message_bus'
3
+
4
+ describe MessageBus::MessageHandler do
5
+
6
+ it "should properly register message handlers" do
7
+ MessageBus::MessageHandler.handle "/hello" do |m|
8
+ m
9
+ end
10
+ MessageBus::MessageHandler.call("site","/hello", "world", 1).should == "world"
11
+ end
12
+
13
+ it "should correctly load message handlers" do
14
+ MessageBus::MessageHandler.load_handlers("#{File.dirname(__FILE__)}/handlers")
15
+ MessageBus::MessageHandler.call("site","/dupe", "1", 1).should == "11"
16
+ end
17
+
18
+ it "should allow for a connect / disconnect callback" do
19
+ MessageBus::MessageHandler.handle "/channel" do |m|
20
+ m
21
+ end
22
+
23
+ connected = false
24
+ disconnected = false
25
+
26
+ MessageBus.on_connect do |site_id|
27
+ connected = true
28
+ end
29
+ MessageBus.on_disconnect do |site_id|
30
+ disconnected = true
31
+ end
32
+
33
+ MessageBus::MessageHandler.call("site_id", "/channel", "data", 1)
34
+
35
+ connected.should == true
36
+ disconnected.should == true
37
+
38
+ end
39
+ end
@@ -0,0 +1,306 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'message_bus'
5
+ require 'rack/test'
6
+
7
+ describe MessageBus::Rack::Middleware do
8
+ include Rack::Test::Methods
9
+
10
+ before do
11
+ MessageBus.long_polling_enabled = false
12
+ end
13
+
14
+ def app
15
+ @app ||= Rack::Builder.new {
16
+ use FakeAsyncMiddleware
17
+ use MessageBus::Rack::Middleware
18
+ run lambda {|env| [500, {'Content-Type' => 'text/html'}, 'should not be called' ]}
19
+ }.to_app
20
+ end
21
+
22
+ shared_examples "long polling" do
23
+ before do
24
+ MessageBus.long_polling_enabled = true
25
+ end
26
+
27
+ it "should respond right away if dlp=t" do
28
+ post "/message-bus/ABC?dlp=t", '/foo1' => 0
29
+ FakeAsyncMiddleware.in_async?.should == false
30
+ last_response.should be_ok
31
+ end
32
+
33
+ it "should respond right away to long polls that are polling on -1 with the last_id" do
34
+ post "/message-bus/ABC", '/foo' => -1
35
+ last_response.should be_ok
36
+ parsed = JSON.parse(last_response.body)
37
+ parsed.length.should == 1
38
+ parsed[0]["channel"].should == "/__status"
39
+ parsed[0]["data"]["/foo"].should == MessageBus.last_id("/foo")
40
+ end
41
+
42
+ it "should respond to long polls when data is available" do
43
+
44
+ Thread.new do
45
+ wait_for(2000) { FakeAsyncMiddleware.in_async? }
46
+ MessageBus.publish "/foo", "םוֹלשָׁ"
47
+ end
48
+
49
+ post "/message-bus/ABC", '/foo' => nil
50
+
51
+ last_response.should be_ok
52
+ parsed = JSON.parse(last_response.body)
53
+ parsed.length.should == 1
54
+ parsed[0]["data"].should == "םוֹלשָׁ"
55
+ end
56
+
57
+ it "should timeout within its alloted slot" do
58
+ begin
59
+ MessageBus.long_polling_interval = 10
60
+ s = Time.now.to_f * 1000
61
+ post "/message-bus/ABC", '/foo' => nil
62
+ (Time.now.to_f * 1000 - s).should < 30
63
+ ensure
64
+ MessageBus.long_polling_interval = 5000
65
+ end
66
+ end
67
+
68
+ it "should support batch filtering" do
69
+ MessageBus.user_id_lookup do |env|
70
+ 1
71
+ end
72
+
73
+ MessageBus.around_client_batch("/demo") do |message, user_ids, callback|
74
+ begin
75
+ Thread.current["test"] = user_ids
76
+ callback.call
77
+ ensure
78
+ Thread.current["test"] = nil
79
+ end
80
+ end
81
+
82
+ test = nil
83
+
84
+ MessageBus.client_filter("/demo") do |user_id, message|
85
+ test = Thread.current["test"]
86
+ message
87
+ end
88
+
89
+ client_id = "ABCD"
90
+
91
+ id = MessageBus.publish("/demo", "test")
92
+
93
+ Thread.new do
94
+ wait_for(2000) { FakeAsyncMiddleware.in_async? }
95
+ MessageBus.publish "/demo", "test"
96
+ end
97
+
98
+ post "/message-bus/#{client_id}", {
99
+ '/demo' => id
100
+ }
101
+
102
+ test.should == [1]
103
+ end
104
+ end
105
+
106
+ describe "thin async" do
107
+ before do
108
+ FakeAsyncMiddleware.simulate_thin_async
109
+ end
110
+ it_behaves_like "long polling"
111
+ end
112
+
113
+ describe "hijack" do
114
+ before do
115
+ FakeAsyncMiddleware.simulate_hijack
116
+ MessageBus.rack_hijack_enabled = true
117
+ end
118
+ it_behaves_like "long polling"
119
+ end
120
+
121
+ describe "diagnostics" do
122
+
123
+ it "should return a 403 if a user attempts to get at the _diagnostics path" do
124
+ get "/message-bus/_diagnostics"
125
+ last_response.status.should == 403
126
+ end
127
+
128
+ it "should get a 200 with html for an authorized user" do
129
+ MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true })
130
+ get "/message-bus/_diagnostics"
131
+ last_response.status.should == 200
132
+ end
133
+
134
+ it "should get the script it asks for" do
135
+ MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true })
136
+ get "/message-bus/_diagnostics/assets/message-bus.js"
137
+ last_response.status.should == 200
138
+ last_response.content_type.should == "text/javascript;"
139
+ end
140
+
141
+ end
142
+
143
+ describe "polling" do
144
+ before do
145
+ MessageBus.long_polling_enabled = false
146
+ end
147
+
148
+ it "should respond with a 200 to a subscribe" do
149
+ client_id = "ABCD"
150
+
151
+ # client always keeps a list of channels with last message id they got on each
152
+ post "/message-bus/#{client_id}", {
153
+ '/foo' => nil,
154
+ '/bar' => nil
155
+ }
156
+ last_response.should be_ok
157
+ end
158
+
159
+ it "should correctly understand that -1 means stuff from now onwards" do
160
+
161
+ MessageBus.publish('foo', 'bar')
162
+
163
+ post "/message-bus/ABCD", {
164
+ '/foo' => -1
165
+ }
166
+ last_response.should be_ok
167
+ parsed = JSON.parse(last_response.body)
168
+ parsed.length.should == 1
169
+ parsed[0]["channel"].should == "/__status"
170
+ parsed[0]["data"]["/foo"].should == MessageBus.last_id("/foo")
171
+
172
+ end
173
+
174
+ it "should respond with the data if messages exist in the backlog" do
175
+ id = MessageBus.last_id('/foo')
176
+
177
+ MessageBus.publish("/foo", "barbs")
178
+ MessageBus.publish("/foo", "borbs")
179
+
180
+ client_id = "ABCD"
181
+ post "/message-bus/#{client_id}", {
182
+ '/foo' => id,
183
+ '/bar' => nil
184
+ }
185
+
186
+ parsed = JSON.parse(last_response.body)
187
+ parsed.length.should == 2
188
+ parsed[0]["data"].should == "barbs"
189
+ parsed[1]["data"].should == "borbs"
190
+ end
191
+
192
+ it "should not get consumed messages" do
193
+ MessageBus.publish("/foo", "barbs")
194
+ id = MessageBus.last_id('/foo')
195
+
196
+ client_id = "ABCD"
197
+ post "/message-bus/#{client_id}", {
198
+ '/foo' => id
199
+ }
200
+
201
+ parsed = JSON.parse(last_response.body)
202
+ parsed.length.should == 0
203
+ end
204
+
205
+ it "should filter by user correctly" do
206
+ id = MessageBus.publish("/foo", "test", user_ids: [1])
207
+ MessageBus.user_id_lookup do |env|
208
+ 0
209
+ end
210
+
211
+ client_id = "ABCD"
212
+ post "/message-bus/#{client_id}", {
213
+ '/foo' => id - 1
214
+ }
215
+
216
+ parsed = JSON.parse(last_response.body)
217
+ parsed.length.should == 0
218
+
219
+ MessageBus.user_id_lookup do |env|
220
+ 1
221
+ end
222
+
223
+ post "/message-bus/#{client_id}", {
224
+ '/foo' => id - 1
225
+ }
226
+
227
+ parsed = JSON.parse(last_response.body)
228
+ parsed.length.should == 1
229
+ end
230
+
231
+
232
+ it "should filter by client_filter correctly" do
233
+ id = MessageBus.publish("/filter", "test")
234
+ user_id = 0
235
+
236
+ MessageBus.user_id_lookup do |env|
237
+ user_id
238
+ end
239
+
240
+ MessageBus.client_filter("/filter") do |user_id, message|
241
+ if user_id == 0
242
+ message = message.dup
243
+ message.data += "_filter"
244
+ message
245
+ elsif user_id == 1
246
+ message
247
+ end
248
+ end
249
+
250
+ client_id = "ABCD"
251
+
252
+ post "/message-bus/#{client_id}", {
253
+ '/filter' => id - 1
254
+ }
255
+
256
+ parsed = JSON.parse(last_response.body)
257
+ parsed[0]['data'].should == "test_filter"
258
+
259
+ user_id = 1
260
+
261
+ post "/message-bus/#{client_id}", {
262
+ '/filter' => id - 1
263
+ }
264
+
265
+ parsed = JSON.parse(last_response.body)
266
+ parsed.length.should == 1
267
+ parsed[0]["data"].should == "test"
268
+
269
+ user_id = 2
270
+
271
+ post "/message-bus/#{client_id}", {
272
+ '/filter' => id - 1
273
+ }
274
+
275
+ parsed = JSON.parse(last_response.body)
276
+ parsed.length.should == 0
277
+ end
278
+
279
+ it "should filter by group correctly" do
280
+ id = MessageBus.publish("/foo", "test", group_ids: [3,4,5])
281
+ MessageBus.group_ids_lookup do |env|
282
+ [0,1,2]
283
+ end
284
+
285
+ client_id = "ABCD"
286
+ post "/message-bus/#{client_id}", {
287
+ '/foo' => id - 1
288
+ }
289
+
290
+ parsed = JSON.parse(last_response.body)
291
+ parsed.length.should == 0
292
+
293
+ MessageBus.group_ids_lookup do |env|
294
+ [1,7,4,100]
295
+ end
296
+
297
+ post "/message-bus/#{client_id}", {
298
+ '/foo' => id - 1
299
+ }
300
+
301
+ parsed = JSON.parse(last_response.body)
302
+ parsed.length.should == 1
303
+ end
304
+ end
305
+
306
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'message_bus'
3
+
4
+ describe MessageBus::ReliablePubSub do
5
+
6
+ def new_bus
7
+ MessageBus::ReliablePubSub.new(:db => 10)
8
+ end
9
+
10
+ def work_it
11
+ Signal.trap("HUP") { exit }
12
+
13
+ bus = new_bus
14
+ $stdout.reopen("/dev/null", "w")
15
+ $stderr.reopen("/dev/null", "w")
16
+ # subscribe blocks, so we need a new bus to transmit
17
+ new_bus.subscribe("/echo", 0) do |msg|
18
+ bus.publish("/response", Process.pid.to_s)
19
+ end
20
+ end
21
+
22
+ def spawn_child
23
+ r = fork
24
+ if r.nil?
25
+ work_it
26
+ else
27
+ r
28
+ end
29
+ end
30
+
31
+ it 'gets every response from child processes' do
32
+ pid = nil
33
+ Redis.new(:db => 10).flushdb
34
+ begin
35
+ pids = (1..10).map{spawn_child}
36
+ responses = []
37
+ bus = MessageBus::ReliablePubSub.new(:db => 10)
38
+ Thread.new do
39
+ bus.subscribe("/response", 0) do |msg|
40
+ responses << msg if pids.include? msg.data.to_i
41
+ end
42
+ end
43
+ 10.times{bus.publish("/echo", Process.pid.to_s)}
44
+ wait_for 4000 do
45
+ responses.count == 100
46
+ end
47
+
48
+ # p responses.group_by(&:data).map{|k,v|[k, v.count]}
49
+ # p responses.group_by(&:global_id).map{|k,v|[k, v.count]}
50
+ responses.count.should == 100
51
+ ensure
52
+ if pids
53
+ pids.each do |pid|
54
+ Process.kill("HUP", pid)
55
+ Process.wait(pid)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'message_bus'
3
+
4
+ describe MessageBus::ReliablePubSub do
5
+
6
+ def new_test_bus
7
+ MessageBus::ReliablePubSub.new(:db => 10)
8
+ end
9
+
10
+ before do
11
+ @bus = new_test_bus
12
+ @bus.reset!
13
+ end
14
+
15
+ it "should be able to access the backlog" do
16
+ @bus.publish "/foo", "bar"
17
+ @bus.publish "/foo", "baz"
18
+
19
+ @bus.backlog("/foo", 0).to_a.should == [
20
+ MessageBus::Message.new(1,1,'/foo','bar'),
21
+ MessageBus::Message.new(2,2,'/foo','baz')
22
+ ]
23
+ end
24
+
25
+ it "should truncate channels correctly" do
26
+ @bus.max_backlog_size = 2
27
+ 4.times do |t|
28
+ @bus.publish "/foo", t.to_s
29
+ end
30
+
31
+ @bus.backlog("/foo").to_a.should == [
32
+ MessageBus::Message.new(3,3,'/foo','2'),
33
+ MessageBus::Message.new(4,4,'/foo','3'),
34
+ ]
35
+ end
36
+
37
+ it "should be able to grab a message by id" do
38
+ id1 = @bus.publish "/foo", "bar"
39
+ id2 = @bus.publish "/foo", "baz"
40
+ @bus.get_message("/foo", id2).should == MessageBus::Message.new(2, 2, "/foo", "baz")
41
+ @bus.get_message("/foo", id1).should == MessageBus::Message.new(1, 1, "/foo", "bar")
42
+ end
43
+
44
+ it "should be able to access the global backlog" do
45
+ @bus.publish "/foo", "bar"
46
+ @bus.publish "/hello", "world"
47
+ @bus.publish "/foo", "baz"
48
+ @bus.publish "/hello", "planet"
49
+
50
+ @bus.global_backlog.to_a.should == [
51
+ MessageBus::Message.new(1, 1, "/foo", "bar"),
52
+ MessageBus::Message.new(2, 1, "/hello", "world"),
53
+ MessageBus::Message.new(3, 2, "/foo", "baz"),
54
+ MessageBus::Message.new(4, 2, "/hello", "planet")
55
+ ]
56
+ end
57
+
58
+ it "should correctly omit dropped messages from the global backlog" do
59
+ @bus.max_backlog_size = 1
60
+ @bus.publish "/foo", "a"
61
+ @bus.publish "/foo", "b"
62
+ @bus.publish "/bar", "a"
63
+ @bus.publish "/bar", "b"
64
+
65
+ @bus.global_backlog.to_a.should == [
66
+ MessageBus::Message.new(2, 2, "/foo", "b"),
67
+ MessageBus::Message.new(4, 2, "/bar", "b")
68
+ ]
69
+ end
70
+
71
+ it "should have the correct number of messages for multi threaded access" do
72
+ threads = []
73
+ 4.times do
74
+ threads << Thread.new do
75
+ bus = new_test_bus
76
+ 25.times {
77
+ bus.publish "/foo", "."
78
+ }
79
+ end
80
+ end
81
+
82
+ threads.each{|t| t.join}
83
+ @bus.backlog("/foo").length == 100
84
+ end
85
+
86
+ it "should be able to subscribe globally with recovery" do
87
+ @bus.publish("/foo", "1")
88
+ @bus.publish("/bar", "2")
89
+ got = []
90
+
91
+ t = Thread.new do
92
+ new_test_bus.global_subscribe(0) do |msg|
93
+ got << msg
94
+ end
95
+ end
96
+
97
+ @bus.publish("/bar", "3")
98
+
99
+ wait_for(100) do
100
+ got.length == 3
101
+ end
102
+
103
+ t.kill
104
+
105
+ got.length.should == 3
106
+ got.map{|m| m.data}.should == ["1","2","3"]
107
+ end
108
+
109
+ it "should be able to encode and decode messages properly" do
110
+ m = MessageBus::Message.new 1,2,'||','||'
111
+ MessageBus::Message.decode(m.encode).should == m
112
+ end
113
+
114
+ it "should handle subscribe on single channel, with recovery" do
115
+ @bus.publish("/foo", "1")
116
+ @bus.publish("/bar", "2")
117
+ got = []
118
+
119
+ t = Thread.new do
120
+ new_test_bus.subscribe("/foo",0) do |msg|
121
+ got << msg
122
+ end
123
+ end
124
+
125
+ @bus.publish("/foo", "3")
126
+
127
+ wait_for(100) do
128
+ got.length == 2
129
+ end
130
+
131
+ t.kill
132
+
133
+ got.map{|m| m.data}.should == ["1","3"]
134
+ end
135
+
136
+ it "should not get backlog if subscribe is called without params" do
137
+ @bus.publish("/foo", "1")
138
+ got = []
139
+
140
+ t = Thread.new do
141
+ new_test_bus.subscribe("/foo") do |msg|
142
+ got << msg
143
+ end
144
+ end
145
+
146
+ # sleep 50ms to allow the bus to correctly subscribe,
147
+ # I thought about adding a subscribed callback, but outside of testing it matters less
148
+ sleep 0.05
149
+
150
+ @bus.publish("/foo", "2")
151
+
152
+ wait_for(100) do
153
+ got.length == 1
154
+ end
155
+
156
+ t.kill
157
+
158
+ got.map{|m| m.data}.should == ["2"]
159
+ end
160
+
161
+ it "should allow us to get last id on a channel" do
162
+ @bus.last_id("/foo").should == 0
163
+ @bus.publish("/foo", "1")
164
+ @bus.last_id("/foo").should == 1
165
+ end
166
+
167
+ end