message_bus 3.3.7 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +1 -8
  3. data/.github/workflows/ci.yml +50 -20
  4. data/.prettierrc +1 -0
  5. data/CHANGELOG +99 -46
  6. data/README.md +31 -53
  7. data/Rakefile +22 -22
  8. data/docker-compose.yml +1 -1
  9. data/lib/message_bus/backends/base.rb +14 -0
  10. data/lib/message_bus/backends/memory.rb +13 -0
  11. data/lib/message_bus/backends/postgres.rb +55 -22
  12. data/lib/message_bus/backends/redis.rb +17 -1
  13. data/lib/message_bus/client.rb +26 -22
  14. data/lib/message_bus/distributed_cache.rb +1 -0
  15. data/lib/message_bus/rack/middleware.rb +0 -6
  16. data/lib/message_bus/rack/thin_ext.rb +1 -0
  17. data/lib/message_bus/version.rb +1 -1
  18. data/lib/message_bus.rb +53 -71
  19. data/message_bus.gemspec +4 -3
  20. data/package.json +2 -5
  21. data/spec/helpers.rb +6 -1
  22. data/spec/lib/fake_async_middleware.rb +1 -0
  23. data/spec/lib/message_bus/backend_spec.rb +20 -3
  24. data/spec/lib/message_bus/client_spec.rb +1 -0
  25. data/spec/lib/message_bus/connection_manager_spec.rb +4 -0
  26. data/spec/lib/message_bus/multi_process_spec.rb +21 -10
  27. data/spec/lib/message_bus/rack/middleware_spec.rb +2 -49
  28. data/spec/lib/message_bus/timer_thread_spec.rb +1 -5
  29. data/spec/lib/message_bus_spec.rb +12 -3
  30. data/spec/performance/backlog.rb +80 -0
  31. data/spec/performance/publish.rb +4 -4
  32. data/spec/spec_helper.rb +1 -1
  33. data/vendor/assets/javascripts/message-bus-ajax.js +38 -0
  34. data/vendor/assets/javascripts/message-bus.js +549 -0
  35. metadata +8 -31
  36. data/assets/application.jsx +0 -121
  37. data/assets/babel.min.js +0 -25
  38. data/assets/react-dom.js +0 -19851
  39. data/assets/react.js +0 -3029
  40. data/examples/diagnostics/Gemfile +0 -6
  41. data/examples/diagnostics/config.ru +0 -22
  42. data/lib/message_bus/diagnostics.rb +0 -62
  43. data/lib/message_bus/rack/diagnostics.rb +0 -120
data/lib/message_bus.rb CHANGED
@@ -7,9 +7,7 @@ require_relative "message_bus/version"
7
7
  require_relative "message_bus/message"
8
8
  require_relative "message_bus/client"
9
9
  require_relative "message_bus/connection_manager"
10
- require_relative "message_bus/diagnostics"
11
10
  require_relative "message_bus/rack/middleware"
12
- require_relative "message_bus/rack/diagnostics"
13
11
  require_relative "message_bus/timer_thread"
14
12
  require_relative "message_bus/codec/base"
15
13
  require_relative "message_bus/backends"
@@ -41,21 +39,10 @@ module MessageBus::Implementation
41
39
  def initialize
42
40
  @config = {}
43
41
  @mutex = Synchronizer.new
44
- end
45
-
46
- # @param [Boolean] val whether or not to cache static assets for the diagnostics pages
47
- # @return [void]
48
- def cache_assets=(val)
49
- configure(cache_assets: val)
50
- end
51
-
52
- # @return [Boolean] whether or not to cache static assets for the diagnostics pages
53
- def cache_assets
54
- if defined? @config[:cache_assets]
55
- @config[:cache_assets]
56
- else
57
- true
58
- end
42
+ @off = false
43
+ @destroyed = false
44
+ @timer_thread = nil
45
+ @subscriber_thread = nil
59
46
  end
60
47
 
61
48
  # @param [Logger] logger a logger object to be used by the bus
@@ -293,15 +280,20 @@ module MessageBus::Implementation
293
280
  @config[:transport_codec] ||= MessageBus::Codec::Json.new
294
281
  end
295
282
 
296
- # @param [MessageBus::Backend::Base] pub_sub a configured backend
283
+ # @param [MessageBus::Backend::Base] backend_instance A configured backend
297
284
  # @return [void]
285
+ def backend_instance=(backend_instance)
286
+ configure(backend_instance: backend_instance)
287
+ end
288
+
298
289
  def reliable_pub_sub=(pub_sub)
299
- configure(reliable_pub_sub: pub_sub)
290
+ logger.warn "MessageBus.reliable_pub_sub= is deprecated, use MessageBus.backend_instance= instead."
291
+ self.backend_instance = pub_sub
300
292
  end
301
293
 
302
294
  # @return [MessageBus::Backend::Base] the configured backend. If not
303
295
  # explicitly set, will be loaded based on the configuration provided.
304
- def reliable_pub_sub
296
+ def backend_instance
305
297
  @mutex.synchronize do
306
298
  return nil if @destroyed
307
299
 
@@ -309,7 +301,7 @@ module MessageBus::Implementation
309
301
  # passed to backend.
310
302
  logger
311
303
 
312
- @config[:reliable_pub_sub] ||= begin
304
+ @config[:backend_instance] ||= begin
313
305
  @config[:backend_options] ||= {}
314
306
  require "message_bus/backends/#{backend}"
315
307
  MessageBus::BACKENDS[backend].new @config
@@ -317,17 +309,16 @@ module MessageBus::Implementation
317
309
  end
318
310
  end
319
311
 
312
+ def reliable_pub_sub
313
+ logger.warn "MessageBus.reliable_pub_sub is deprecated, use MessageBus.backend_instance instead."
314
+ backend_instance
315
+ end
316
+
320
317
  # @return [Symbol] the name of the backend implementation configured
321
318
  def backend
322
319
  @config[:backend] || :redis
323
320
  end
324
321
 
325
- # Enables diagnostics tracking
326
- # @return [void]
327
- def enable_diagnostics
328
- MessageBus::Diagnostics.enable(self)
329
- end
330
-
331
322
  # Publishes a message to a channel
332
323
  #
333
324
  # @param [String] channel the name of the channel to which the message should be published
@@ -394,7 +385,7 @@ module MessageBus::Implementation
394
385
  end
395
386
 
396
387
  encoded_channel_name = encode_channel_name(channel, site_id)
397
- reliable_pub_sub.publish(encoded_channel_name, encoded_data, channel_opts)
388
+ backend_instance.publish(encoded_channel_name, encoded_data, channel_opts)
398
389
  end
399
390
 
400
391
  # Subscribe to messages. Each message will be delivered by yielding to the
@@ -410,9 +401,9 @@ module MessageBus::Implementation
410
401
  # @return [void]
411
402
  def blocking_subscribe(channel = nil, &blk)
412
403
  if channel
413
- reliable_pub_sub.subscribe(encode_channel_name(channel), &blk)
404
+ backend_instance.subscribe(encode_channel_name(channel), &blk)
414
405
  else
415
- reliable_pub_sub.global_subscribe(&blk)
406
+ backend_instance.global_subscribe(&blk)
416
407
  end
417
408
  end
418
409
 
@@ -491,9 +482,9 @@ module MessageBus::Implementation
491
482
  def backlog(channel = nil, last_id = nil, site_id = nil)
492
483
  old =
493
484
  if channel
494
- reliable_pub_sub.backlog(encode_channel_name(channel, site_id), last_id)
485
+ backend_instance.backlog(encode_channel_name(channel, site_id), last_id)
495
486
  else
496
- reliable_pub_sub.global_backlog(last_id)
487
+ backend_instance.global_backlog(last_id)
497
488
  end
498
489
 
499
490
  old.each do |m|
@@ -509,7 +500,19 @@ module MessageBus::Implementation
509
500
  #
510
501
  # @return [Integer] the channel-specific ID of the last message published to the given channel
511
502
  def last_id(channel, site_id = nil)
512
- reliable_pub_sub.last_id(encode_channel_name(channel, site_id))
503
+ backend_instance.last_id(encode_channel_name(channel, site_id))
504
+ end
505
+
506
+ # Get the ID of the last message published on multiple channels
507
+ #
508
+ # @param [Array<String>] channels - array of channels to fetch
509
+ # @param [String] site_id - the ID of the site by which to filter
510
+ #
511
+ # @return [Hash] the channel-specific IDs of the last message published to each requested channel
512
+ def last_ids(*channels, site_id: nil)
513
+ encoded_channel_names = channels.map { |c| encode_channel_name(c, site_id) }
514
+ ids = backend_instance.last_ids(*encoded_channel_names)
515
+ channels.zip(ids).to_h
513
516
  end
514
517
 
515
518
  # Get the last message published on a channel
@@ -532,7 +535,8 @@ module MessageBus::Implementation
532
535
  def destroy
533
536
  return if @destroyed
534
537
 
535
- reliable_pub_sub.global_unsubscribe
538
+ backend_instance.global_unsubscribe
539
+ backend_instance.destroy
536
540
 
537
541
  @mutex.synchronize do
538
542
  return if @destroyed
@@ -550,7 +554,7 @@ module MessageBus::Implementation
550
554
  # scheduled tasks.
551
555
  # @return [void]
552
556
  def after_fork
553
- reliable_pub_sub.after_fork
557
+ backend_instance.after_fork
554
558
  ensure_subscriber_thread
555
559
  # will ensure timer is running
556
560
  timer.queue {}
@@ -559,12 +563,12 @@ module MessageBus::Implementation
559
563
  # @return [Boolean] whether or not the server is actively listening for
560
564
  # publications on the bus
561
565
  def listening?
562
- @subscriber_thread && @subscriber_thread.alive?
566
+ @subscriber_thread&.alive?
563
567
  end
564
568
 
565
569
  # (see MessageBus::Backend::Base#reset!)
566
570
  def reset!
567
- reliable_pub_sub.reset! if reliable_pub_sub
571
+ backend_instance.reset! if backend_instance
568
572
  end
569
573
 
570
574
  # @return [MessageBus::TimerThread] the timer thread used for triggering
@@ -692,12 +696,6 @@ module MessageBus::Implementation
692
696
  @subscriptions[site_id][channel] << blk
693
697
  ensure_subscriber_thread
694
698
 
695
- attempts = 100
696
- while attempts > 0 && !reliable_pub_sub.subscribed
697
- sleep 0.001
698
- attempts -= 1
699
- end
700
-
701
699
  raise MessageBus::BusDestroyed if @destroyed
702
700
 
703
701
  blk
@@ -715,10 +713,17 @@ module MessageBus::Implementation
715
713
 
716
714
  def ensure_subscriber_thread
717
715
  @mutex.synchronize do
718
- return if (@subscriber_thread && @subscriber_thread.alive?) || @destroyed
716
+ return if @destroyed
717
+ next if @subscriber_thread&.alive?
719
718
 
720
719
  @subscriber_thread = new_subscriber_thread
721
720
  end
721
+
722
+ attempts = 100
723
+ while attempts > 0 && !backend_instance.subscribed
724
+ sleep 0.001
725
+ attempts -= 1
726
+ end
722
727
  end
723
728
 
724
729
  MIN_KEEPALIVE = 20
@@ -739,34 +744,11 @@ module MessageBus::Implementation
739
744
  if !@destroyed && thread.alive? && keepalive_interval > MIN_KEEPALIVE
740
745
 
741
746
  publish("/__mb_keepalive__/", Process.pid, user_ids: [-1])
742
- # going for x3 keepalives missed for a restart, need to ensure this only very rarely happens
743
- # note: after_fork will sort out a bad @last_message date, but thread will be dead anyway
744
747
  if (Time.now - (@last_message || Time.now)) > keepalive_interval * 3
745
- logger.warn "Global messages on #{Process.pid} timed out, restarting process"
746
- # No other clean way to remove this thread, its listening on a socket
747
- # no data is arriving
748
- #
749
- # In production we see this kind of situation ... sometimes ... when there is
750
- # a VRRP failover, or weird networking condition
751
- pid = Process.pid
752
-
753
- # do the best we can to terminate self cleanly
754
- fork do
755
- Process.kill('TERM', pid)
756
- sleep 10
757
- begin
758
- Process.kill('KILL', pid)
759
- rescue Errno::ESRCH
760
- logger.warn "#{Process.pid} successfully terminated by `TERM` signal."
761
- end
762
- end
763
-
764
- sleep 10
765
- Process.kill('KILL', pid)
766
-
767
- else
768
- timer.queue(keepalive_interval, &blk) if keepalive_interval > MIN_KEEPALIVE
748
+ logger.warn "Global messages on #{Process.pid} timed out, message bus is no longer functioning correctly"
769
749
  end
750
+
751
+ timer.queue(keepalive_interval, &blk) if keepalive_interval > MIN_KEEPALIVE
770
752
  end
771
753
  end
772
754
 
@@ -778,7 +760,7 @@ module MessageBus::Implementation
778
760
  def global_subscribe_thread
779
761
  # pretend we just got a message
780
762
  @last_message = Time.now
781
- reliable_pub_sub.global_subscribe do |msg|
763
+ backend_instance.global_subscribe do |msg|
782
764
  begin
783
765
  @last_message = Time.now
784
766
  decode_message!(msg)
data/message_bus.gemspec CHANGED
@@ -9,9 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.summary = %q{}
10
10
  gem.homepage = "https://github.com/discourse/message_bus"
11
11
  gem.license = "MIT"
12
- gem.files = `git ls-files`.split($\)
13
- gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
12
+ gem.files = `git ls-files`.split($\) +
13
+ ["vendor/assets/javascripts/message-bus.js", "vendor/assets/javascripts/message-bus-ajax.js"]
15
14
  gem.name = "message_bus"
16
15
  gem.require_paths = ["lib"]
17
16
  gem.version = MessageBus::VERSION
@@ -19,9 +18,11 @@ Gem::Specification.new do |gem|
19
18
 
20
19
  gem.add_runtime_dependency 'rack', '>= 1.1.3'
21
20
 
21
+ # Optional runtime dependencies
22
22
  gem.add_development_dependency 'redis'
23
23
  gem.add_development_dependency 'pg'
24
24
  gem.add_development_dependency 'concurrent-ruby' # for distributed-cache
25
+
25
26
  gem.add_development_dependency 'minitest'
26
27
  gem.add_development_dependency 'minitest-hooks'
27
28
  gem.add_development_dependency 'minitest-global_expectations'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "message-bus-client",
3
- "version": "3.3.5",
3
+ "version": "0.0.0-version-placeholder",
4
4
  "description": "A message bus client in Javascript",
5
5
  "main": "assets/message-bus.js",
6
6
  "keywords": [
@@ -12,10 +12,7 @@
12
12
  ],
13
13
  "jsnext:main": "assets/message-bus.js",
14
14
  "module": "assets/message-bus.js",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/discourse/message_bus.git"
18
- },
15
+ "repository": "https://github.com/discourse/message_bus",
19
16
  "author": "Sam Saffron, Robin Ward",
20
17
  "license": "MIT",
21
18
  "bugs": {
data/spec/helpers.rb CHANGED
@@ -25,7 +25,12 @@ def test_config_for_backend(backend)
25
25
  when :redis
26
26
  config[:url] = ENV['REDISURL']
27
27
  when :postgres
28
- config[:backend_options] = { host: ENV['PGHOST'], user: ENV['PGUSER'] || ENV['USER'], password: ENV['PGPASSWORD'], dbname: ENV['PGDATABASE'] || 'message_bus_test' }
28
+ config[:backend_options] = {
29
+ host: ENV['PGHOST'],
30
+ user: ENV['PGUSER'] || ENV['USER'],
31
+ password: ENV['PGPASSWORD'],
32
+ dbname: ENV['PGDATABASE'] || 'message_bus_test'
33
+ }
29
34
  end
30
35
  config
31
36
  end
@@ -7,6 +7,7 @@ class FakeAsyncMiddleware
7
7
  @simulate_thin_async = false
8
8
  @simulate_hijack = false
9
9
  @in_async = false
10
+ @allow_chunked = false
10
11
  end
11
12
 
12
13
  def app
@@ -3,13 +3,14 @@
3
3
  require_relative '../../spec_helper'
4
4
  require 'message_bus'
5
5
 
6
- describe PUB_SUB_CLASS do
6
+ describe BACKEND_CLASS do
7
7
  before do
8
- @bus = PUB_SUB_CLASS.new(test_config_for_backend(CURRENT_BACKEND))
8
+ @bus = BACKEND_CLASS.new(test_config_for_backend(CURRENT_BACKEND))
9
9
  end
10
10
 
11
11
  after do
12
12
  @bus.reset!
13
+ @bus.destroy
13
14
  end
14
15
 
15
16
  describe "API parity" do
@@ -29,7 +30,7 @@ describe PUB_SUB_CLASS do
29
30
  end
30
31
 
31
32
  it "should initialize with max_backlog_size" do
32
- PUB_SUB_CLASS.new({}, 2000).max_backlog_size.must_equal 2000
33
+ BACKEND_CLASS.new({}, 2000).max_backlog_size.must_equal 2000
33
34
  end
34
35
 
35
36
  it "should truncate channels correctly" do
@@ -90,6 +91,22 @@ describe PUB_SUB_CLASS do
90
91
  @bus.last_id("/foo").must_equal 1
91
92
  end
92
93
 
94
+ it "should allow us to get multiple last_ids" do
95
+ @bus.last_ids("/foo", "/bar", "/foobar").must_equal [0, 0, 0]
96
+
97
+ @bus.publish("/foo", "one")
98
+ @bus.publish("/foo", "two")
99
+ @bus.publish("/foobar", "three")
100
+
101
+ @bus.last_ids("/foo", "/bar", "/foobar").must_equal(
102
+ [
103
+ @bus.last_id("/foo"),
104
+ @bus.last_id("/bar"),
105
+ @bus.last_id("/foobar")
106
+ ]
107
+ )
108
+ end
109
+
93
110
  it "can set backlog age" do
94
111
  @bus.max_backlog_age = 1
95
112
 
@@ -16,6 +16,7 @@ describe MessageBus::Client do
16
16
  end
17
17
 
18
18
  after do
19
+ @client.close
19
20
  @bus.reset!
20
21
  @bus.destroy
21
22
  end
@@ -6,6 +6,10 @@ require 'message_bus'
6
6
  class FakeAsync
7
7
  attr_accessor :cleanup_timer
8
8
 
9
+ def initialize
10
+ @sent = nil
11
+ end
12
+
9
13
  def <<(val)
10
14
  sleep 0.01 # simulate IO
11
15
  @sent ||= +""
@@ -3,7 +3,7 @@
3
3
  require_relative '../../spec_helper'
4
4
  require 'message_bus'
5
5
 
6
- describe PUB_SUB_CLASS do
6
+ describe BACKEND_CLASS do
7
7
  def self.error!
8
8
  @error = true
9
9
  end
@@ -13,18 +13,20 @@ describe PUB_SUB_CLASS do
13
13
  end
14
14
 
15
15
  def new_bus
16
- PUB_SUB_CLASS.new(test_config_for_backend(CURRENT_BACKEND).merge(db: 10))
16
+ BACKEND_CLASS.new(test_config_for_backend(CURRENT_BACKEND).merge(db: 10))
17
17
  end
18
18
 
19
19
  def work_it
20
20
  bus = new_bus
21
- $stdout.reopen("/dev/null", "w")
22
- $stderr.reopen("/dev/null", "w")
23
- # subscribe blocks, so we need a new bus to transmit
24
- new_bus.subscribe("/echo", 0) do |msg|
25
- bus.publish("/response", "#{msg.data}-#{Process.pid.to_s}")
21
+ bus.subscribe("/echo", 0) do |msg|
22
+ if msg.data == "done"
23
+ bus.global_unsubscribe
24
+ else
25
+ bus.publish("/response", "#{msg.data}-#{Process.pid.to_s}")
26
+ end
26
27
  end
27
28
  ensure
29
+ bus.destroy
28
30
  exit!(0)
29
31
  end
30
32
 
@@ -44,12 +46,14 @@ describe PUB_SUB_CLASS do
44
46
  test_never :memory
45
47
  skip("previous error") if self.class.error?
46
48
  GC.start
47
- new_bus.reset!
49
+ bus = new_bus
50
+ bus.reset!
51
+
48
52
  begin
49
53
  pids = (1..10).map { spawn_child }
50
54
  expected_responses = pids.map { |x| (0...10).map { |i| "0#{i}-#{x}" } }.flatten
51
55
  unexpected_responses = []
52
- bus = new_bus
56
+
53
57
  t = Thread.new do
54
58
  bus.subscribe("/response", 0) do |msg|
55
59
  if expected_responses.include?(msg.data)
@@ -59,10 +63,14 @@ describe PUB_SUB_CLASS do
59
63
  end
60
64
  end
61
65
  end
66
+
62
67
  10.times { |i| bus.publish("/echo", "0#{i}") }
63
- wait_for 4000 do
68
+
69
+ wait_for(2000) do
64
70
  expected_responses.empty?
65
71
  end
72
+
73
+ bus.publish("/echo", "done")
66
74
  bus.global_unsubscribe
67
75
  t.join
68
76
 
@@ -81,7 +89,10 @@ describe PUB_SUB_CLASS do
81
89
  Process.wait(pid)
82
90
  end
83
91
  end
92
+
84
93
  bus.global_unsubscribe
94
+ bus.reset!
95
+ bus.destroy
85
96
  end
86
97
  end
87
98
  end
@@ -83,7 +83,7 @@ describe MessageBus::Rack::Middleware do
83
83
  { "FOO" => "BAR" }
84
84
  end
85
85
 
86
- Thread.new do
86
+ t = Thread.new do
87
87
  wait_for(2000) { middleware.in_async? }
88
88
  bus.publish "/foo", "םוֹלשָׁ"
89
89
  end
@@ -96,6 +96,7 @@ describe MessageBus::Rack::Middleware do
96
96
  parsed[0]["data"].must_equal "םוֹלשָׁ"
97
97
 
98
98
  last_response.headers["FOO"].must_equal "BAR"
99
+ t.join
99
100
  end
100
101
 
101
102
  it "should timeout within its alloted slot" do
@@ -141,54 +142,6 @@ describe MessageBus::Rack::Middleware do
141
142
  end
142
143
  end
143
144
 
144
- describe "diagnostics" do
145
- it "should return a 403 if an unauthorized user attempts to get at the _diagnostics path" do
146
- get "/message-bus/_diagnostics"
147
- last_response.status.must_equal 403
148
- end
149
-
150
- it "should get a 200 with html for an authorized user" do
151
- def @bus.is_admin_lookup
152
- proc { |_| true }
153
- end
154
-
155
- get "/message-bus/_diagnostics"
156
- last_response.status.must_equal 200
157
- end
158
-
159
- describe "with an altered base_route" do
160
- let(:base_route) { "/base/route/" }
161
-
162
- it "should get a 200 with html for an authorized user" do
163
- def @bus.is_admin_lookup
164
- proc { |_| true }
165
- end
166
-
167
- get "/base/route/message-bus/_diagnostics"
168
- last_response.status.must_equal 200
169
- end
170
- end
171
-
172
- it "should get the script it asks for" do
173
- def @bus.is_admin_lookup
174
- proc { |_| true }
175
- end
176
-
177
- get "/message-bus/_diagnostics/assets/message-bus.js"
178
- last_response.status.must_equal 200
179
- last_response.content_type.must_equal "application/javascript;charset=UTF-8"
180
- end
181
-
182
- it "should return 404 for invalid assets path" do
183
- def @bus.is_admin_lookup
184
- proc { |_| true }
185
- end
186
-
187
- get "/message-bus/_diagnostics/assets/../Gemfile"
188
- last_response.status.must_equal 404
189
- end
190
- end
191
-
192
145
  describe "polling" do
193
146
  before do
194
147
  @bus.long_polling_enabled = false
@@ -52,10 +52,6 @@ describe MessageBus::TimerThread do
52
52
  it "should call the error callback if something goes wrong" do
53
53
  error = nil
54
54
 
55
- @timer.queue do
56
- boom
57
- end
58
-
59
55
  @timer.on_error do |e|
60
56
  error = e
61
57
  end
@@ -64,7 +60,7 @@ describe MessageBus::TimerThread do
64
60
  boom
65
61
  end
66
62
 
67
- wait_for(10) do
63
+ wait_for(100) do
68
64
  error
69
65
  end
70
66
 
@@ -23,7 +23,8 @@ describe MessageBus do
23
23
  @bus.off?.must_equal true
24
24
  end
25
25
 
26
- it "can call destroy twice" do
26
+ it "can call destroy multiple times" do
27
+ @bus.destroy
27
28
  @bus.destroy
28
29
  @bus.destroy
29
30
  end
@@ -36,6 +37,14 @@ describe MessageBus do
36
37
  @bus.after_fork
37
38
  end
38
39
 
40
+ it "destroying immediately after `after_fork` does not lock" do
41
+ 10.times do
42
+ @bus.on
43
+ @bus.after_fork
44
+ @bus.destroy
45
+ end
46
+ end
47
+
39
48
  describe "#base_route=" do
40
49
  it "adds leading and trailing slashes" do
41
50
  @bus.base_route = "my/base/route"
@@ -106,7 +115,7 @@ describe MessageBus do
106
115
  @bus.publish("/chuck", norris: true)
107
116
  @bus.publish("/chuck", norris: true)
108
117
 
109
- @bus.reliable_pub_sub.reset!
118
+ @bus.backend_instance.reset!
110
119
 
111
120
  @bus.publish("/chuck", yeager: true)
112
121
 
@@ -124,7 +133,7 @@ describe MessageBus do
124
133
  @bus.publish("/chuck", norris: true)
125
134
  @bus.publish("/chuck", norris: true)
126
135
 
127
- @bus.reliable_pub_sub.expire_all_backlogs!
136
+ @bus.backend_instance.expire_all_backlogs!
128
137
 
129
138
  @bus.publish("/chuck", yeager: true)
130
139
 
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'lib')
4
+ require 'logger'
5
+ require 'benchmark'
6
+ require 'message_bus'
7
+
8
+ require_relative "../helpers"
9
+
10
+ backends = ENV['MESSAGE_BUS_BACKENDS'].split(",").map(&:to_sym)
11
+ channel = "/foo"
12
+ iterations = 10_000
13
+ results = []
14
+
15
+ puts "Running backlog benchmark with #{iterations} iterations on backends: #{backends.inspect}"
16
+
17
+ run_benchmark = lambda do |bm, backend|
18
+ bus = MessageBus::Instance.new
19
+ bus.configure(test_config_for_backend(backend))
20
+
21
+ bus.backend_instance.max_backlog_size = 100
22
+ bus.backend_instance.max_global_backlog_size = 1000
23
+
24
+ channel_names = 10.times.map { |i| "channel#{i}" }
25
+
26
+ 100.times do |i|
27
+ channel_names.each do |ch|
28
+ bus.publish(ch, { message_number_is: i })
29
+ end
30
+ end
31
+
32
+ last_ids = channel_names.map { |ch| [ch, bus.last_id(ch)] }.to_h
33
+
34
+ 1000.times do
35
+ # Warmup
36
+ client = MessageBus::Client.new(message_bus: bus)
37
+ channel_names.each { |ch| client.subscribe(ch, -1) }
38
+ client.backlog
39
+ end
40
+
41
+ bm.report("#{backend} - #backlog with no backlogs requested") do
42
+ iterations.times do
43
+ client = MessageBus::Client.new(message_bus: bus)
44
+ channel_names.each { |ch| client.subscribe(ch, -1) }
45
+ client.backlog
46
+ end
47
+ end
48
+
49
+ (0..5).each do |ch_i|
50
+ channels_with_messages = (ch_i) * 2
51
+
52
+ bm.report("#{backend} - #backlog when #{channels_with_messages}/10 channels have new messages") do
53
+ iterations.times do
54
+ client = MessageBus::Client.new(message_bus: bus)
55
+ channel_names.each_with_index do |ch, i|
56
+ client.subscribe(ch, last_ids[ch] + ((i < channels_with_messages) ? -1 : 0))
57
+ end
58
+ result = client.backlog
59
+ if result.length != channels_with_messages
60
+ raise "Result has #{result.length} messages. Expected #{channels_with_messages}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ bus.reset!
67
+ bus.destroy
68
+ end
69
+
70
+ puts
71
+
72
+ Benchmark.benchmark(" duration\n", 60, "%10.2rs\n", "") do |bm|
73
+ backends.each do |backend|
74
+ run_benchmark.call(bm, backend)
75
+ end
76
+ end
77
+ puts
78
+ results.each do |result|
79
+ puts result
80
+ end