pgbus 0.1.3 → 0.1.5

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/pgbus/outbox_controller.rb +10 -0
  3. data/app/controllers/pgbus/queues_controller.rb +10 -0
  4. data/app/helpers/pgbus/application_helper.rb +6 -0
  5. data/app/models/pgbus/outbox_entry.rb +10 -0
  6. data/app/models/pgbus/queue_state.rb +33 -0
  7. data/app/views/layouts/pgbus/application.html.erb +1 -0
  8. data/app/views/pgbus/dashboard/_stats_cards.html.erb +7 -1
  9. data/app/views/pgbus/outbox/index.html.erb +55 -0
  10. data/app/views/pgbus/queues/_queues_list.html.erb +15 -1
  11. data/config/routes.rb +4 -0
  12. data/lib/generators/pgbus/add_outbox_generator.rb +52 -0
  13. data/lib/generators/pgbus/add_queue_states_generator.rb +51 -0
  14. data/lib/generators/pgbus/templates/add_outbox.rb.erb +25 -0
  15. data/lib/generators/pgbus/templates/add_queue_states.rb.erb +16 -0
  16. data/lib/pgbus/active_job/adapter.rb +6 -5
  17. data/lib/pgbus/active_job/executor.rb +22 -5
  18. data/lib/pgbus/circuit_breaker.rb +112 -0
  19. data/lib/pgbus/client.rb +140 -49
  20. data/lib/pgbus/configuration.rb +54 -2
  21. data/lib/pgbus/dedup_cache.rb +76 -0
  22. data/lib/pgbus/engine.rb +6 -0
  23. data/lib/pgbus/event_bus/handler.rb +13 -2
  24. data/lib/pgbus/outbox/poller.rb +117 -0
  25. data/lib/pgbus/outbox.rb +30 -0
  26. data/lib/pgbus/process/dispatcher.rb +46 -0
  27. data/lib/pgbus/process/heartbeat.rb +3 -1
  28. data/lib/pgbus/process/lifecycle.rb +111 -0
  29. data/lib/pgbus/process/supervisor.rb +40 -5
  30. data/lib/pgbus/process/worker.rb +86 -19
  31. data/lib/pgbus/rate_counter.rb +81 -0
  32. data/lib/pgbus/recurring/schedule.rb +1 -1
  33. data/lib/pgbus/version.rb +1 -1
  34. data/lib/pgbus/web/data_source.rb +87 -2
  35. data/lib/pgbus.rb +35 -6
  36. data/lib/tasks/pgbus_pgmq.rake +5 -3
  37. metadata +15 -1
@@ -11,6 +11,8 @@ module Pgbus
11
11
  CONCURRENCY_INTERVAL = 300 # Run concurrency cleanup every 5 minutes
12
12
  BATCH_CLEANUP_INTERVAL = 3600 # Run batch cleanup every hour
13
13
  RECURRING_CLEANUP_INTERVAL = 3600 # Run recurring execution cleanup every hour
14
+ ARCHIVE_COMPACTION_INTERVAL = 3600 # Run archive compaction every hour
15
+ OUTBOX_CLEANUP_INTERVAL = 3600 # Run outbox cleanup every hour
14
16
 
15
17
  attr_reader :config
16
18
 
@@ -22,6 +24,8 @@ module Pgbus
22
24
  @last_concurrency_at = Time.now
23
25
  @last_batch_cleanup_at = Time.now
24
26
  @last_recurring_cleanup_at = Time.now
27
+ @last_archive_compaction_at = Time.now
28
+ @last_outbox_cleanup_at = Time.now
25
29
  end
26
30
 
27
31
  def run
@@ -64,6 +68,8 @@ module Pgbus
64
68
  run_if_due(now, :@last_concurrency_at, CONCURRENCY_INTERVAL) { cleanup_concurrency }
65
69
  run_if_due(now, :@last_batch_cleanup_at, BATCH_CLEANUP_INTERVAL) { cleanup_batches }
66
70
  run_if_due(now, :@last_recurring_cleanup_at, RECURRING_CLEANUP_INTERVAL) { cleanup_recurring_executions }
71
+ run_if_due(now, :@last_archive_compaction_at, archive_compaction_interval) { compact_archives }
72
+ run_if_due(now, :@last_outbox_cleanup_at, OUTBOX_CLEANUP_INTERVAL) { cleanup_outbox }
67
73
  end
68
74
 
69
75
  # Only update the timestamp when the block succeeds.
@@ -121,6 +127,46 @@ module Pgbus
121
127
  Pgbus.logger.warn { "[Pgbus] Batch cleanup failed: #{e.message}" }
122
128
  end
123
129
 
130
+ def cleanup_outbox
131
+ return unless config.outbox_enabled
132
+
133
+ retention = config.outbox_retention
134
+ return unless retention&.positive?
135
+
136
+ deleted = OutboxEntry.published_before(Time.now.utc - retention).delete_all
137
+ Pgbus.logger.debug { "[Pgbus] Cleaned up #{deleted} published outbox entries" } if deleted.positive?
138
+ rescue StandardError => e
139
+ Pgbus.logger.warn { "[Pgbus] Outbox cleanup failed: #{e.message}" }
140
+ end
141
+
142
+ def archive_compaction_interval
143
+ config.archive_compaction_interval || ARCHIVE_COMPACTION_INTERVAL
144
+ end
145
+
146
+ def compact_archives
147
+ retention = config.archive_retention
148
+ return unless retention&.positive?
149
+
150
+ cutoff = Time.now.utc - retention
151
+ batch_size = config.archive_compaction_batch_size || 1000
152
+ prefix = config.queue_prefix
153
+
154
+ conn = config.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
155
+ queue_names = conn.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
156
+
157
+ queue_names.each do |full_name|
158
+ next unless full_name.start_with?("#{prefix}_")
159
+
160
+ stripped = full_name.delete_prefix("#{prefix}_")
161
+ deleted = Pgbus.client.purge_archive(stripped, older_than: cutoff, batch_size: batch_size)
162
+ Pgbus.logger.debug { "[Pgbus] Compacted #{deleted} archive entries from #{full_name}" } if deleted.positive?
163
+ rescue StandardError => e
164
+ Pgbus.logger.warn { "[Pgbus] Archive compaction failed for #{full_name}: #{e.message}" }
165
+ end
166
+ rescue StandardError => e
167
+ Pgbus.logger.warn { "[Pgbus] Archive compaction failed: #{e.message}" }
168
+ end
169
+
124
170
  def cleanup_recurring_executions
125
171
  retention = config.recurring_execution_retention
126
172
  return unless retention&.positive?
@@ -11,9 +11,10 @@ module Pgbus
11
11
 
12
12
  attr_reader :process_entry
13
13
 
14
- def initialize(kind:, metadata: {})
14
+ def initialize(kind:, metadata: {}, on_beat: nil)
15
15
  @kind = kind
16
16
  @metadata = metadata
17
+ @on_beat = on_beat
17
18
  @timer = nil
18
19
  end
19
20
 
@@ -31,6 +32,7 @@ module Pgbus
31
32
  def beat
32
33
  return unless @process_id
33
34
 
35
+ @on_beat&.call
34
36
  ProcessEntry.where(id: @process_id).update_all(last_heartbeat_at: Time.current)
35
37
  rescue StandardError => e
36
38
  Pgbus.logger.warn { "[Pgbus] Heartbeat failed: #{e.message}" }
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Pgbus
6
+ module Process
7
+ # Thread-safe worker lifecycle state machine inspired by LavinMQ's QueueState.
8
+ #
9
+ # States:
10
+ # :starting → initial state, setting up
11
+ # :running → actively processing messages
12
+ # :paused → temporarily stopped (manual or circuit breaker)
13
+ # :draining → finishing in-flight work before stopping
14
+ # :stopped → terminal state
15
+ #
16
+ # Transitions:
17
+ # starting → running
18
+ # running → paused | draining | stopped
19
+ # paused → running | draining | stopped
20
+ # draining → stopped
21
+ class Lifecycle
22
+ STATES = %i[starting running paused draining stopped].freeze
23
+
24
+ TRANSITIONS = {
25
+ starting: %i[running stopped],
26
+ running: %i[paused draining stopped],
27
+ paused: %i[running draining stopped],
28
+ draining: %i[stopped],
29
+ stopped: []
30
+ }.freeze
31
+
32
+ attr_reader :state
33
+
34
+ def initialize
35
+ @state = :starting
36
+ @mutex = Mutex.new
37
+ @callbacks = Hash.new { |h, k| h[k] = [] }
38
+ end
39
+
40
+ def transition_to!(new_state)
41
+ @mutex.synchronize do
42
+ validate_transition!(new_state)
43
+ old_state = @state
44
+ @state = new_state
45
+ fire_callbacks(old_state, new_state)
46
+ new_state
47
+ end
48
+ end
49
+
50
+ def transition_to(new_state)
51
+ transition_to!(new_state)
52
+ rescue InvalidTransition
53
+ false
54
+ end
55
+
56
+ def on(event, &block)
57
+ @callbacks[event] << block
58
+ end
59
+
60
+ def starting?
61
+ @state == :starting
62
+ end
63
+
64
+ def running?
65
+ @state == :running
66
+ end
67
+
68
+ def paused?
69
+ @state == :paused
70
+ end
71
+
72
+ def draining?
73
+ @state == :draining
74
+ end
75
+
76
+ def stopped?
77
+ @state == :stopped
78
+ end
79
+
80
+ def active?
81
+ running? || paused?
82
+ end
83
+
84
+ def can_process?
85
+ running?
86
+ end
87
+
88
+ def terminal?
89
+ stopped?
90
+ end
91
+
92
+ private
93
+
94
+ def validate_transition!(new_state)
95
+ raise ArgumentError, "Unknown state: #{new_state}. Valid states: #{STATES.join(", ")}" unless STATES.include?(new_state)
96
+
97
+ return if TRANSITIONS[@state].include?(new_state)
98
+
99
+ raise InvalidTransition, "Cannot transition from #{@state} to #{new_state}. " \
100
+ "Valid transitions: #{TRANSITIONS[@state].join(", ")}"
101
+ end
102
+
103
+ def fire_callbacks(old_state, new_state)
104
+ @callbacks[:"#{old_state}_to_#{new_state}"].each(&:call)
105
+ @callbacks[:any].each { |cb| cb.call(old_state, new_state) }
106
+ end
107
+ end
108
+
109
+ class InvalidTransition < Pgbus::Error; end
110
+ end
111
+ end
@@ -55,6 +55,9 @@ module Pgbus
55
55
 
56
56
  # Boot event consumers if configured
57
57
  boot_consumers
58
+
59
+ # Boot outbox poller if configured
60
+ boot_outbox_poller
58
61
  end
59
62
 
60
63
  def fork_worker(worker_config)
@@ -63,7 +66,7 @@ module Pgbus
63
66
 
64
67
  pid = fork do
65
68
  restore_signals
66
- setup_child_signals
69
+ setup_child_process
67
70
  load_rails_app
68
71
  worker = Worker.new(queues: queues, threads: threads, config: config)
69
72
  worker.run
@@ -83,7 +86,7 @@ module Pgbus
83
86
  def fork_dispatcher
84
87
  pid = fork do
85
88
  restore_signals
86
- setup_child_signals
89
+ setup_child_process
87
90
  load_rails_app
88
91
  dispatcher = Dispatcher.new(config: config)
89
92
  dispatcher.run
@@ -110,7 +113,7 @@ module Pgbus
110
113
  def fork_scheduler
111
114
  pid = fork do
112
115
  restore_signals
113
- setup_child_signals
116
+ setup_child_process
114
117
  load_rails_app
115
118
  load_recurring_config
116
119
  scheduler = Recurring::Scheduler.new(config: config)
@@ -165,7 +168,7 @@ module Pgbus
165
168
 
166
169
  pid = fork do
167
170
  restore_signals
168
- setup_child_signals
171
+ setup_child_process
169
172
  load_rails_app
170
173
  consumer = Consumer.new(topics: topics, threads: threads, config: config)
171
174
  consumer.run
@@ -182,6 +185,32 @@ module Pgbus
182
185
  Pgbus.logger.error { "[Pgbus] Fork failed for consumer: #{e.message}" }
183
186
  end
184
187
 
188
+ def boot_outbox_poller
189
+ return unless config.outbox_enabled
190
+
191
+ fork_outbox_poller
192
+ end
193
+
194
+ def fork_outbox_poller
195
+ pid = fork do
196
+ restore_signals
197
+ setup_child_process
198
+ load_rails_app
199
+ poller = Outbox::Poller.new(config: config)
200
+ poller.run
201
+ end
202
+
203
+ unless pid
204
+ Pgbus.logger.error { "[Pgbus] Failed to fork outbox poller" }
205
+ return
206
+ end
207
+
208
+ @forks[pid] = { type: :outbox_poller }
209
+ Pgbus.logger.info { "[Pgbus] Forked outbox poller pid=#{pid}" }
210
+ rescue Errno::EAGAIN, Errno::ENOMEM => e
211
+ Pgbus.logger.error { "[Pgbus] Fork failed for outbox poller: #{e.message}" }
212
+ end
213
+
185
214
  def monitor_loop
186
215
  loop do
187
216
  break if @shutting_down && @forks.empty?
@@ -223,6 +252,8 @@ module Pgbus
223
252
  fork_scheduler
224
253
  when :consumer
225
254
  fork_consumer(info[:config])
255
+ when :outbox_poller
256
+ fork_outbox_poller
226
257
  end
227
258
  end
228
259
 
@@ -234,7 +265,11 @@ module Pgbus
234
265
  end
235
266
  end
236
267
 
237
- def setup_child_signals
268
+ def setup_child_process
269
+ # Reset the PGMQ client so this forked process gets a fresh
270
+ # PG::Connection instead of inheriting the parent's (which is
271
+ # in undefined state post-fork and not thread-safe to share).
272
+ Pgbus.reset_client!
238
273
  %w[INT TERM QUIT].each do |sig|
239
274
  trap(sig) { @shutting_down = true }
240
275
  end
@@ -13,31 +13,44 @@ module Pgbus
13
13
  @queues = Array(queues)
14
14
  @threads = threads
15
15
  @config = config
16
- @shutting_down = false
16
+ @lifecycle = Lifecycle.new
17
17
  @jobs_processed = Concurrent::AtomicFixnum.new(0)
18
18
  @jobs_failed = Concurrent::AtomicFixnum.new(0)
19
+ @in_flight = Concurrent::AtomicFixnum.new(0)
20
+ @rate_counter = RateCounter.new(:processed, :failed, :dequeued)
19
21
  @started_at = Time.now
20
22
  @executor = Pgbus::ActiveJob::Executor.new
21
23
  @pool = Concurrent::FixedThreadPool.new(threads)
24
+ @circuit_breaker = Pgbus::CircuitBreaker.new(config: config)
22
25
  end
23
26
 
24
27
  def stats
25
- { jobs_processed: @jobs_processed.value, jobs_failed: @jobs_failed.value, started_at: @started_at }
28
+ {
29
+ jobs_processed: @jobs_processed.value,
30
+ jobs_failed: @jobs_failed.value,
31
+ in_flight: @in_flight.value,
32
+ state: @lifecycle.state,
33
+ rates: @rate_counter.rates,
34
+ started_at: @started_at
35
+ }
26
36
  end
27
37
 
28
38
  def run
29
39
  setup_signals
30
40
  start_heartbeat
31
41
  resolve_wildcard_queues
42
+ @lifecycle.transition_to!(:running)
32
43
  Pgbus.logger.info { "[Pgbus] Worker started: queues=#{queues.join(",")} threads=#{threads} pid=#{::Process.pid}" }
33
44
 
34
45
  loop do
35
46
  process_signals
36
- break if @shutting_down && @pool.queue_length.zero?
37
- break if recycle_needed? && @pool.queue_length.zero?
47
+ check_recycle
38
48
 
39
- claim_and_execute unless @shutting_down || recycle_needed?
40
- interruptible_sleep(config.polling_interval) if (@shutting_down || recycle_needed?) && !@pool.queue_length.zero?
49
+ break if @lifecycle.stopped?
50
+ break if @lifecycle.draining? && @pool.queue_length.zero?
51
+
52
+ claim_and_execute if @lifecycle.can_process?
53
+ interruptible_sleep(config.polling_interval) if @lifecycle.draining? || @lifecycle.paused?
41
54
  end
42
55
 
43
56
  shutdown
@@ -45,12 +58,12 @@ module Pgbus
45
58
 
46
59
  def graceful_shutdown
47
60
  Pgbus.logger.info { "[Pgbus] Worker shutting down gracefully..." }
48
- @shutting_down = true
61
+ @lifecycle.transition_to(:draining)
49
62
  end
50
63
 
51
64
  def immediate_shutdown
52
65
  Pgbus.logger.warn { "[Pgbus] Worker shutting down immediately!" }
53
- @shutting_down = true
66
+ @lifecycle.transition_to!(:stopped)
54
67
  @pool.kill
55
68
  end
56
69
 
@@ -60,6 +73,13 @@ module Pgbus
60
73
  idle = @pool.max_length - @pool.queue_length
61
74
  return interruptible_sleep(config.polling_interval) if idle <= 0
62
75
 
76
+ if config.prefetch_limit
77
+ available = config.prefetch_limit - @in_flight.value
78
+ return interruptible_sleep(config.polling_interval) if available <= 0
79
+
80
+ idle = [idle, available].min
81
+ end
82
+
63
83
  tagged_messages = fetch_messages(idle)
64
84
 
65
85
  if tagged_messages.empty?
@@ -67,21 +87,28 @@ module Pgbus
67
87
  return
68
88
  end
69
89
 
70
- tagged_messages.each do |queue_name, message|
71
- @pool.post { process_message(message, queue_name) }
90
+ @rate_counter.increment(:dequeued, tagged_messages.size)
91
+ tagged_messages.each do |queue_name, message, source_queue|
92
+ @in_flight.increment
93
+ @pool.post { process_message(message, queue_name, source_queue: source_queue) }
72
94
  end
73
95
  end
74
96
 
75
97
  # Returns an array of [queue_name, message] pairs so we always know
76
98
  # which queue each message came from (PGMQ messages don't carry this).
77
99
  def fetch_messages(qty)
78
- if queues.size == 1
79
- queue = queues.first
100
+ active_queues = queues.reject { |q| @circuit_breaker.paused?(q) }
101
+ return [] if active_queues.empty?
102
+
103
+ if priority_enabled?
104
+ fetch_prioritized(active_queues, qty)
105
+ elsif active_queues.size == 1
106
+ queue = active_queues.first
80
107
  messages = Pgbus.client.read_batch(queue, qty: qty) || []
81
108
  messages.map { |m| [queue, m] }
82
109
  else
83
- per_queue = [(qty / queues.size.to_f).ceil, 1].max
84
- queues.flat_map do |q|
110
+ per_queue = [(qty / active_queues.size.to_f).ceil, 1].max
111
+ active_queues.flat_map do |q|
85
112
  (Pgbus.client.read_batch(q, qty: per_queue) || []).map { |m| [q, m] }
86
113
  end.first(qty)
87
114
  end
@@ -90,13 +117,45 @@ module Pgbus
90
117
  []
91
118
  end
92
119
 
93
- def process_message(message, queue_name)
94
- result = @executor.execute(message, queue_name)
120
+ def fetch_prioritized(active_queues, qty)
121
+ remaining = qty
122
+ results = []
123
+
124
+ active_queues.each do |q|
125
+ break if remaining <= 0
126
+
127
+ batch = Pgbus.client.read_batch_prioritized(q, qty: remaining)
128
+ batch.each do |physical_queue, message|
129
+ results << [q, message, physical_queue]
130
+ end
131
+ remaining -= batch.size
132
+ end
133
+
134
+ results
135
+ end
136
+
137
+ def priority_enabled?
138
+ config.priority_levels && config.priority_levels > 1
139
+ end
140
+
141
+ def process_message(message, queue_name, source_queue: nil)
142
+ result = @executor.execute(message, queue_name, source_queue: source_queue)
95
143
  @jobs_processed.increment
96
- @jobs_failed.increment if result == :failed
144
+ @rate_counter.increment(:processed)
145
+ if result == :failed
146
+ @jobs_failed.increment
147
+ @rate_counter.increment(:failed)
148
+ @circuit_breaker.record_failure(queue_name)
149
+ else
150
+ @circuit_breaker.record_success(queue_name)
151
+ end
97
152
  rescue StandardError => e
98
153
  @jobs_failed.increment
154
+ @rate_counter.increment(:failed)
155
+ @circuit_breaker.record_failure(queue_name)
99
156
  Pgbus.logger.error { "[Pgbus] Unhandled error processing message: #{e.message}" }
157
+ ensure
158
+ @in_flight.decrement
100
159
  end
101
160
 
102
161
  # Resolve "*" to all non-DLQ queues from pgmq.meta, stripping the prefix.
@@ -107,7 +166,8 @@ module Pgbus
107
166
  dlq_suffix = config.dead_letter_queue_suffix
108
167
  prefix = "#{config.queue_prefix}_"
109
168
 
110
- all_queues = ActiveRecord::Base.connection.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
169
+ conn = Pgbus.configuration.connects_to ? Pgbus::ApplicationRecord.connection : ActiveRecord::Base.connection
170
+ all_queues = conn.select_values("SELECT queue_name FROM pgmq.meta ORDER BY queue_name")
111
171
  resolved = all_queues
112
172
  .reject { |q| q.end_with?(dlq_suffix) }
113
173
  .map { |q| q.delete_prefix(prefix) }
@@ -124,6 +184,12 @@ module Pgbus
124
184
  @queues = [config.default_queue]
125
185
  end
126
186
 
187
+ def check_recycle
188
+ return unless @lifecycle.running? && recycle_needed?
189
+
190
+ @lifecycle.transition_to(:draining)
191
+ end
192
+
127
193
  def recycle_needed?
128
194
  exceeded_max_jobs? || exceeded_max_memory? || exceeded_max_lifetime?
129
195
  end
@@ -164,7 +230,8 @@ module Pgbus
164
230
  def start_heartbeat
165
231
  @heartbeat = Heartbeat.new(
166
232
  kind: "worker",
167
- metadata: { queues: queues, threads: threads, pid: ::Process.pid }
233
+ metadata: { queues: queues, threads: threads, pid: ::Process.pid },
234
+ on_beat: -> { @rate_counter.snapshot! }
168
235
  )
169
236
  @heartbeat.start
170
237
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Pgbus
6
+ # Thread-safe rate counter inspired by LavinMQ's rate_stats macro.
7
+ # Tracks absolute counts via AtomicFixnum and computes rates as
8
+ # deltas between periodic snapshots.
9
+ #
10
+ # Usage:
11
+ # counter = RateCounter.new(:enqueued, :dequeued, :failed)
12
+ # counter.increment(:enqueued)
13
+ # counter.snapshot! # call periodically (e.g. every 5s)
14
+ # counter.rate(:enqueued) # => msgs/s since last snapshot
15
+ # counter.rates # => { enqueued: 12.4, dequeued: 10.1, failed: 0.2 }
16
+ class RateCounter
17
+ attr_reader :names
18
+
19
+ def initialize(*names)
20
+ @names = names.map(&:to_sym)
21
+ @counters = {}
22
+ @last_values = {}
23
+ @rates = {}
24
+ @last_snapshot_at = monotonic_now
25
+
26
+ @names.each do |name|
27
+ @counters[name] = Concurrent::AtomicFixnum.new(0)
28
+ @last_values[name] = 0
29
+ @rates[name] = 0.0
30
+ end
31
+ end
32
+
33
+ def increment(name, delta = 1)
34
+ @counters.fetch(name).increment(delta)
35
+ end
36
+
37
+ def count(name)
38
+ @counters.fetch(name).value
39
+ end
40
+
41
+ def rate(name)
42
+ @rates.fetch(name)
43
+ end
44
+
45
+ def rates
46
+ @names.to_h { |name| [name, @rates[name]] }
47
+ end
48
+
49
+ def counts
50
+ @names.to_h { |name| [name, @counters[name].value] }
51
+ end
52
+
53
+ def snapshot!
54
+ now = monotonic_now
55
+ elapsed = now - @last_snapshot_at
56
+ return if elapsed <= 0
57
+
58
+ @names.each do |name|
59
+ current = @counters[name].value
60
+ delta = current - @last_values[name]
61
+ @rates[name] = (delta / elapsed).round(1)
62
+ @last_values[name] = current
63
+ end
64
+
65
+ @last_snapshot_at = now
66
+ end
67
+
68
+ def to_h
69
+ {
70
+ counts: counts,
71
+ rates: rates
72
+ }
73
+ end
74
+
75
+ private
76
+
77
+ def monotonic_now
78
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
79
+ end
80
+ end
81
+ end
@@ -87,7 +87,7 @@ module Pgbus
87
87
  end
88
88
 
89
89
  def resolve_queue(task)
90
- @config.queue_name(task.queue_name || @config.default_queue)
90
+ task.queue_name || @config.default_queue
91
91
  end
92
92
 
93
93
  def build_headers(task, run_at)
data/lib/pgbus/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pgbus
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end