message_bus 0.0.2 → 0.9.3

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.

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