pgbus 0.16.7 → 0.17.1

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.
@@ -14,6 +14,7 @@ module Pgbus
14
14
  class VersionNotFoundError < Pgbus::Error; end
15
15
 
16
16
  SCHEMA_DIR = File.expand_path("pgmq_schema", __dir__).freeze
17
+ FIXUPS_DIR = File.join(SCHEMA_DIR, "fixups").freeze
17
18
 
18
19
  class << self
19
20
  # Returns the latest vendored PGMQ version string.
@@ -23,9 +24,37 @@ module Pgbus
23
24
 
24
25
  # Returns sorted list of all vendored PGMQ versions.
25
26
  def available_versions
26
- Dir.glob(File.join(SCHEMA_DIR, "pgmq_v*.sql"))
27
- .map { |f| File.basename(f).match(/pgmq_v(.+)\.sql/)[1] }
28
- .sort_by { |v| Gem::Version.new(v) }
27
+ versions_in(SCHEMA_DIR)
28
+ end
29
+
30
+ # Versions that ship a table fixup, in version order.
31
+ #
32
+ # An upgrade drops every pgmq function and composite type and re-runs the
33
+ # target version's schema, which recreates both — but never touches an
34
+ # existing table. An upstream hop that ALTERs one (1.13.0 moves
35
+ # partitioned queues' msg_id from GENERATED ALWAYS to BY DEFAULT) needs
36
+ # its own step, and that is what these files are.
37
+ def fixup_versions
38
+ versions_in(FIXUPS_DIR)
39
+ end
40
+
41
+ # Concatenated fixups for every version in (after, upto], in version
42
+ # order. Returns "" when the hop crosses none.
43
+ #
44
+ # @param after [String, nil] the installed version; nil (no recorded
45
+ # version) applies every fixup up to the target, which is safe because
46
+ # each one is idempotent.
47
+ # @param upto [String] the version being upgraded to
48
+ def fixups_sql(after:, upto: latest_version)
49
+ ceiling = Gem::Version.new(upto)
50
+ floor = after && Gem::Version.new(after)
51
+
52
+ applicable = fixup_versions.select do |version|
53
+ candidate = Gem::Version.new(version)
54
+ candidate <= ceiling && (floor.nil? || candidate > floor)
55
+ end
56
+
57
+ applicable.map { |version| File.read(File.join(FIXUPS_DIR, "pgmq_v#{version}.sql")) }.join("\n")
29
58
  end
30
59
 
31
60
  # Returns the filesystem path to the vendored SQL file for a given version.
@@ -177,6 +206,15 @@ module Pgbus
177
206
 
178
207
  private
179
208
 
209
+ # Vendored versions discovered in a directory, in version order. Schema
210
+ # files and fixups share this so the two can never drift into different
211
+ # discovery rules.
212
+ def versions_in(dir)
213
+ Dir.glob(File.join(dir, "pgmq_v*.sql"))
214
+ .map { |f| File.basename(f).match(/pgmq_v(.+)\.sql/)[1] }
215
+ .sort_by { |v| Gem::Version.new(v) }
216
+ end
217
+
180
218
  # Strips extension-specific blocks (pg_extension_config_dump, pg_depend checks)
181
219
  # that only work when pgmq is installed as an extension.
182
220
  def strip_extension_only_blocks(sql)
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgbus
4
+ module Process
5
+ # The read-ahead buffer a Worker or Consumer owns (issue #486).
6
+ #
7
+ # Under database latency the claim loop used to issue one read round trip
8
+ # per job: once the pool is full, slots free one at a time and each loop
9
+ # step read `qty = free slots` = 1. With `read_ahead = N` the loop claims
10
+ # up to N messages beyond its free slots and parks them here, so one read
11
+ # returns a batch and the pool never waits on the reader.
12
+ #
13
+ # Surplus claims cannot live inside the execution pool: AsyncPool#post
14
+ # raises at zero capacity and a queued block could not be handed back on
15
+ # drain. So the buffer is drained into the pool only while it has a free
16
+ # slot, and returned to the queue (vt: 0) on drain, recycle or shutdown.
17
+ #
18
+ # Every buffered message is kept invisible by a VisibilityHeartbeat hold
19
+ # from claim until the run starts, under the executor's key
20
+ # (`source_queue || queue_name`, `prefixed: source_queue.nil?`), so the
21
+ # run's own tracking takes over the same entry with no gap. The hold does
22
+ # not know the job class, so a job that opted out with
23
+ # `pgbus_visibility_heartbeat false` is still held while it waits here:
24
+ # the opt-out covers its run, not the wait. With the heartbeat disabled
25
+ # globally there is no hold at all (Configuration#validate! warns).
26
+ #
27
+ # Only ever touched from the owning process's loop thread, so no mutex.
28
+ class ClaimBuffer
29
+ Claim = Struct.new(:queue_name, :message, :source_queue, :hold)
30
+
31
+ HOLD_LABEL = "(read-ahead)"
32
+
33
+ def initialize(config: Pgbus.configuration)
34
+ @config = config
35
+ @claims = []
36
+ end
37
+
38
+ def size
39
+ @claims.size
40
+ end
41
+
42
+ def empty?
43
+ @claims.empty?
44
+ end
45
+
46
+ def push(queue_name, message, source_queue = nil, client: Pgbus.client)
47
+ hold = VisibilityHeartbeat.hold(
48
+ client: client, queue_name: source_queue || queue_name, prefixed: source_queue.nil?,
49
+ msg_id: message.msg_id, job_class: HOLD_LABEL, config: @config
50
+ )
51
+ @claims << Claim.new(queue_name, message, source_queue, hold)
52
+ self
53
+ end
54
+
55
+ def shift
56
+ @claims.shift
57
+ end
58
+
59
+ # Yields the oldest claims, one per free pool slot, and returns how many.
60
+ # Capacity is re-read before every claim: the block's post takes a slot.
61
+ def drain_into(pool)
62
+ drained = 0
63
+ while !@claims.empty? && pool.available_capacity.positive?
64
+ yield @claims.shift
65
+ drained += 1
66
+ end
67
+ drained
68
+ end
69
+
70
+ # How many messages the next read should claim: enough to fill the free
71
+ # slots and top the buffer back up to `read_ahead`, never more than
72
+ # `prefetch_room` (prefetch_limit minus everything already claimed).
73
+ # With read_ahead 0 and an empty buffer this is `free_slots`: the
74
+ # pre-#486 qty.
75
+ def deficit(free_slots:, read_ahead:, prefetch_room: nil)
76
+ want = free_slots + read_ahead - @claims.size
77
+ want = [want, prefetch_room].min if prefetch_room
78
+ want.clamp(0..)
79
+ end
80
+
81
+ # Hand every buffered message back to the queue right away instead of
82
+ # leaving it invisible until its timeout. Their read_ct has already been
83
+ # bumped, the same cost a crash or a stale claim pays. A failed return
84
+ # is logged and skipped: the hold is already dropped, so the message
85
+ # reappears when its current timeout runs out. Returns the count. One
86
+ # claim at a time, so #size stays true while the returns are written.
87
+ def return_all!(client: Pgbus.client)
88
+ returned = 0
89
+ while (claim = @claims.shift)
90
+ return_claim(claim, client)
91
+ returned += 1
92
+ end
93
+ returned
94
+ end
95
+
96
+ private
97
+
98
+ # The hold goes first: a heartbeat tick that still held the entry could
99
+ # otherwise write its extension after the vt: 0 and hide the message
100
+ # again for a full visibility_timeout. (VisibilityHeartbeat#extend!
101
+ # re-checks registration, which shrinks the remaining window to the
102
+ # tick's own check-then-write.)
103
+ def return_claim(claim, client)
104
+ VisibilityHeartbeat.release(claim.hold)
105
+ queue = claim.source_queue || claim.queue_name
106
+ client.set_visibility_timeout(queue, claim.message.msg_id.to_i, vt: 0, prefixed: claim.source_queue.nil?)
107
+ rescue StandardError => e
108
+ Pgbus.logger.warn do
109
+ "[Pgbus] Could not return read-ahead message msg_id=#{claim.message.msg_id} queue=#{queue}: " \
110
+ "#{e.class}: #{e.message}"
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -7,7 +7,7 @@ module Pgbus
7
7
  class Consumer
8
8
  include SignalHandler
9
9
 
10
- attr_reader :topics, :threads, :config, :execution_mode,
10
+ attr_reader :topics, :threads, :config, :execution_mode, :read_ahead,
11
11
  :queue_names, :wake_signal, :notify_retry_backoff, :circuit_breaker
12
12
  # notify_listener is writable so tests can simulate a start_notify_listener
13
13
  # success from inside a stub (production sets it in start_notify_listener).
@@ -30,6 +30,11 @@ module Pgbus
30
30
  @jobs_processed.value
31
31
  end
32
32
 
33
+ # Messages claimed by read-ahead and not yet handed to the pool (#486).
34
+ def buffered
35
+ @claim_buffer.size
36
+ end
37
+
33
38
  # Seed the processed-job counter. Used by tests to drive the recycle
34
39
  # thresholds without running thousands of real jobs; production only ever
35
40
  # increments it via the AtomicFixnum during message handling.
@@ -60,7 +65,7 @@ module Pgbus
60
65
  queue_names: nil, liveness_pipe: nil, stat_buffer: :default,
61
66
  notify_listener: nil, notify_retry_at: 0.0,
62
67
  notify_retry_backoff: NOTIFY_RETRY_BASE_SECONDS,
63
- started_at_monotonic: nil, wake_pipe: nil)
68
+ started_at_monotonic: nil, wake_pipe: nil, read_ahead: nil)
64
69
  @topics = Array(topics)
65
70
  @threads = threads
66
71
  @config = config
@@ -68,6 +73,8 @@ module Pgbus
68
73
  @shutting_down = false
69
74
  @recycling = false
70
75
  @jobs_processed = Concurrent::AtomicFixnum.new(0)
76
+ @read_ahead = read_ahead || config.read_ahead
77
+ @claim_buffer = ClaimBuffer.new(config: config)
71
78
  @loop_tick_at = Concurrent::AtomicReference.new(nil)
72
79
  @started_at_monotonic = started_at_monotonic || monotonic_now
73
80
  @wake_signal = WakeSignal.new
@@ -134,7 +141,12 @@ module Pgbus
134
141
  check_recycle
135
142
  ensure_notify_listener
136
143
 
137
- break if @shutting_down
144
+ if @shutting_down
145
+ # No drain loop here: buffered claims go straight back to the
146
+ # queue; shutdown then waits for the ones already running.
147
+ @claim_buffer.return_all!(client: Pgbus.client) unless @claim_buffer.empty?
148
+ break
149
+ end
138
150
 
139
151
  consume
140
152
  @stat_buffer&.flush_if_due
@@ -171,20 +183,35 @@ module Pgbus
171
183
  @queue_names = @registry.queue_names_for_topics(topics)
172
184
  end
173
185
 
186
+ # Same loop step as Worker#claim_and_execute (issue #486): feed
187
+ # buffered claims to free slots, read the deficit, feed again, and wait
188
+ # only when the step moved nothing.
174
189
  def consume
175
- idle = @pool.available_capacity
176
- return @wake_signal.wait(timeout: wake_timeout) if idle <= 0
177
-
178
- tagged_messages = fetch_messages(idle)
179
-
180
- if tagged_messages.empty?
181
- @wake_signal.wait(timeout: wake_timeout)
182
- return
190
+ drained = drain_claim_buffer
191
+ claimed = claim_deficit
192
+ drained += drain_claim_buffer
193
+ @wake_signal.wait(timeout: wake_timeout) if drained.zero? && claimed.zero?
194
+ end
195
+
196
+ # The hold is released before handle_message's own heartbeat tracking
197
+ # registers the same key, so the message is never untracked in between.
198
+ def drain_claim_buffer
199
+ @claim_buffer.drain_into(@pool) do |claim|
200
+ @pool.post do
201
+ VisibilityHeartbeat.release(claim.hold)
202
+ handle_message(claim.message, claim.queue_name)
203
+ end
183
204
  end
205
+ end
184
206
 
185
- tagged_messages.each do |queue_name, message|
186
- @pool.post { handle_message(message, queue_name) }
187
- end
207
+ def claim_deficit
208
+ want = @claim_buffer.deficit(free_slots: @pool.available_capacity, read_ahead: @read_ahead)
209
+ return 0 if want.zero?
210
+
211
+ tagged_messages = fetch_messages(want)
212
+ client = Pgbus.client
213
+ tagged_messages.each { |queue_name, message| @claim_buffer.push(queue_name, message, client: client) }
214
+ tagged_messages.size
188
215
  end
189
216
 
190
217
  # Returns an array of [queue_name, message] pairs. Queues whose circuit
@@ -171,7 +171,7 @@ module Pgbus
171
171
  private_constant :ROLE_FLAGS
172
172
 
173
173
  def log_boot_banner
174
- Pgbus.logger.info { "[Pgbus] boot: pgbus #{Pgbus::VERSION} pid=#{::Process.pid}" }
174
+ Pgbus.logger.info { "[Pgbus] boot: pgbus #{Pgbus::VERSION} pid=#{::Process.pid} jit=#{RubyJit.label}" }
175
175
  Pgbus.logger.info do
176
176
  "[Pgbus] boot: connection=#{redacted_connection_target} pool=#{banner_field { config.resolved_pool_size }}"
177
177
  end
@@ -330,6 +330,7 @@ module Pgbus
330
330
  queues: queues, threads: threads, config: config,
331
331
  single_active_consumer: single_active, consumer_priority: priority,
332
332
  execution_mode: exec_mode, group_mode: grp_mode,
333
+ read_ahead: config.read_ahead_for(worker_config),
333
334
  liveness_pipe: liveness_writer, wake_pipe: wake_reader
334
335
  )
335
336
  worker.run
@@ -498,6 +499,7 @@ module Pgbus
498
499
  setup_child_process
499
500
  load_rails_app
500
501
  consumer = Consumer.new(topics: topics, threads: threads, config: config,
502
+ read_ahead: config.read_ahead_for(consumer_config),
501
503
  liveness_pipe: liveness_writer, wake_pipe: wake_reader)
502
504
  consumer.run
503
505
  end
@@ -7,7 +7,7 @@ module Pgbus
7
7
  class Worker
8
8
  include SignalHandler
9
9
 
10
- attr_reader :queues, :threads, :config, :execution_mode,
10
+ attr_reader :queues, :threads, :config, :execution_mode, :read_ahead,
11
11
  :rate_counter, :wake_signal, :restore_streak, :lifecycle
12
12
  # stat_buffer is writable so a test can swap in a buffer double after
13
13
  # construction to assert graceful_shutdown / check_recycle flush it. The
@@ -35,7 +35,7 @@ module Pgbus
35
35
  rate_counter: nil, wake_signal: nil, stat_buffer: :default,
36
36
  notify_listener: nil, notify_retry_at: 0.0,
37
37
  notify_retry_backoff: NOTIFY_RETRY_BASE_SECONDS,
38
- started_at_monotonic: nil, wake_pipe: nil)
38
+ started_at_monotonic: nil, wake_pipe: nil, read_ahead: nil)
39
39
  @queues = Array(queues)
40
40
  @initial_queues = @queues.dup.freeze
41
41
  @wildcard = @queues.include?("*")
@@ -65,7 +65,10 @@ module Pgbus
65
65
  @last_wildcard_resolve = nil
66
66
  @jobs_processed = Concurrent::AtomicFixnum.new(0)
67
67
  @jobs_failed = Concurrent::AtomicFixnum.new(0)
68
+ # in_flight counts every claimed message: buffered and running.
68
69
  @in_flight = Concurrent::AtomicFixnum.new(0)
70
+ @read_ahead = read_ahead || config.read_ahead
71
+ @claim_buffer = ClaimBuffer.new(config: config)
69
72
  @loop_tick_at = Concurrent::AtomicReference.new(nil)
70
73
  @rate_counter = rate_counter || RateCounter.new(:processed, :failed, :dequeued)
71
74
  @started_at = Time.current
@@ -119,6 +122,7 @@ module Pgbus
119
122
  jobs_processed: @jobs_processed.value,
120
123
  jobs_failed: @jobs_failed.value,
121
124
  in_flight: @in_flight.value,
125
+ buffered: @claim_buffer.size,
122
126
  state: @lifecycle.state,
123
127
  execution_mode: @execution_mode,
124
128
  consumer_priority: @consumer_priority,
@@ -184,6 +188,9 @@ module Pgbus
184
188
  check_recycle
185
189
  refresh_wildcard_queues
186
190
  ensure_notify_listener
191
+ # Draining, paused or stopped: claims nobody will run here go back
192
+ # to the queue now rather than when their timeout runs out.
193
+ return_claims unless @lifecycle.can_process?
187
194
 
188
195
  break if @lifecycle.stopped?
189
196
  # quiesced? (all slots free), not idle? (any slot free) — exiting
@@ -240,32 +247,53 @@ module Pgbus
240
247
 
241
248
  private
242
249
 
250
+ # One loop step (issue #486): feed buffered claims to free slots, read
251
+ # the deficit (free slots + read_ahead - buffered, capped by
252
+ # prefetch_limit), feed again. With read_ahead 0 the buffer never
253
+ # outlives the step and qty is the free slot count, as before. Waits
254
+ # only when the step moved nothing, so a full pool with a topped-up
255
+ # buffer sleeps until a slot frees (the pool's on_state_change wakes it).
243
256
  def claim_and_execute
244
- poll_interval = wake_timeout
245
-
246
- idle = @pool.available_capacity
247
- return @wake_signal.wait(timeout: poll_interval) if idle <= 0
248
-
249
- if config.prefetch_limit
250
- available = config.prefetch_limit - @in_flight.value
251
- return @wake_signal.wait(timeout: poll_interval) if available <= 0
252
-
253
- idle = [idle, available].min
257
+ drained = drain_claim_buffer
258
+ claimed = claim_deficit
259
+ drained += drain_claim_buffer
260
+ @wake_signal.wait(timeout: wake_timeout) if drained.zero? && claimed.zero?
261
+ end
262
+
263
+ # The hold is released before the executor's own heartbeat tracking
264
+ # registers the same key, so the message is never untracked in between.
265
+ def drain_claim_buffer
266
+ @claim_buffer.drain_into(@pool) do |claim|
267
+ @pool.post do
268
+ VisibilityHeartbeat.release(claim.hold)
269
+ process_message(claim.message, claim.queue_name, source_queue: claim.source_queue)
270
+ end
254
271
  end
272
+ end
255
273
 
256
- tagged_messages = fetch_messages(idle)
274
+ def claim_deficit
275
+ prefetch_room = config.prefetch_limit && (config.prefetch_limit - @in_flight.value)
276
+ want = @claim_buffer.deficit(free_slots: @pool.available_capacity, read_ahead: @read_ahead,
277
+ prefetch_room: prefetch_room)
278
+ return 0 if want.zero?
257
279
 
258
- if tagged_messages.empty?
259
- @wake_signal.wait(timeout: poll_interval)
260
- return
261
- end
280
+ tagged_messages = fetch_messages(want)
281
+ return 0 if tagged_messages.empty?
262
282
 
263
283
  @rate_counter.increment(:dequeued, tagged_messages.size)
284
+ client = Pgbus.client
264
285
  tagged_messages.each do |queue_name, message, source_queue|
265
286
  detect_zombie(queue_name, message)
266
287
  @in_flight.increment
267
- @pool.post { process_message(message, queue_name, source_queue: source_queue) }
288
+ @claim_buffer.push(queue_name, message, source_queue, client: client)
268
289
  end
290
+ tagged_messages.size
291
+ end
292
+
293
+ def return_claims
294
+ return if @claim_buffer.empty?
295
+
296
+ @in_flight.decrement(@claim_buffer.return_all!(client: Pgbus.client))
269
297
  end
270
298
 
271
299
  # Returns an array of [queue_name, message] pairs so we always know
@@ -831,7 +859,8 @@ module Pgbus
831
859
  "rates" => @rate_counter.rates.transform_keys(&:to_s),
832
860
  "jobs_processed" => @jobs_processed.value,
833
861
  "jobs_failed" => @jobs_failed.value,
834
- "in_flight" => @in_flight.value
862
+ "in_flight" => @in_flight.value,
863
+ "buffered" => @claim_buffer.size
835
864
  }
836
865
  end
837
866
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pgbus
4
+ # Reports which Ruby JIT this process runs under (issue #484). Read-only:
5
+ # Rails owns turning YJIT on (config.yjit), pgbus only makes the state
6
+ # visible in the boot log and `pgbus doctor`.
7
+ module RubyJit
8
+ module_function
9
+
10
+ def label
11
+ return "yjit" if yjit_available? && RubyVM::YJIT.enabled?
12
+ return "zjit" if defined?(RubyVM::ZJIT) && RubyVM::ZJIT.enabled?
13
+
14
+ "none"
15
+ end
16
+
17
+ def yjit_available?
18
+ defined?(RubyVM::YJIT) ? true : false
19
+ end
20
+ end
21
+ end
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.16.7"
4
+ VERSION = "0.17.1"
5
5
  end
@@ -30,8 +30,9 @@ module Pgbus
30
30
  #
31
31
  # The ninth member takes the struct out of the 80-byte slot it used to fit
32
32
  # (measured: 80 → 160). That is paid at most once per in-flight message, so
33
- # the whole table is bounded by the execution pool's capacity — a handful of
34
- # entries per process, not one per enqueued job.
33
+ # the whole table is bounded by the execution pool's capacity plus the
34
+ # read-ahead buffer (threads + read_ahead per process, issue #486) — a
35
+ # handful of entries per process, not one per enqueued job.
35
36
  Entry = Struct.new(:client, :queue_name, :prefixed, :msg_id, :job_class, :extended_at, :extensions,
36
37
  :concurrency, :on_beat, keyword_init: true)
37
38
 
@@ -81,6 +82,25 @@ module Pgbus
81
82
  end
82
83
  end
83
84
 
85
+ # Keep a claimed-but-not-started message invisible until #release.
86
+ # Used by Process::ClaimBuffer for read-ahead (issue #486): a buffered
87
+ # message waits for a free slot and must not be redelivered meanwhile.
88
+ # Same key as #track, so the run's own tracking takes over after the
89
+ # hold is released. Returns the entry, or nil when the heartbeat is off.
90
+ def hold(client:, queue_name:, msg_id:, prefixed: true, job_class: nil, config: Pgbus.configuration)
91
+ return unless config.visibility_heartbeat
92
+
93
+ entry = Entry.new(client: client, queue_name: queue_name, prefixed: prefixed, msg_id: msg_id.to_i,
94
+ job_class: job_class, extended_at: monotonic_now, extensions: 0)
95
+ register(entry, config)
96
+ entry
97
+ end
98
+
99
+ # Drop a #hold. A nil entry (heartbeat off) is a no-op.
100
+ def release(entry)
101
+ unregister(entry) if entry
102
+ end
103
+
84
104
  # Extend every tracked message whose last extension is older than the
85
105
  # heartbeat interval. Public so tests and callers without the thread
86
106
  # can drive it.
@@ -127,11 +147,22 @@ module Pgbus
127
147
  end
128
148
  end
129
149
 
150
+ # Only the entry that is registered under the key: a stale hold released
151
+ # after #track re-registered the same message must not drop the running
152
+ # job's entry (issue #486).
130
153
  def unregister(entry)
131
- synchronize { entries.delete(key_for(entry)) }
154
+ synchronize do
155
+ key = key_for(entry)
156
+ entries.delete(key) if entries[key].equal?(entry)
157
+ end
132
158
  end
133
159
 
134
160
  def extend!(entry, now:, config:)
161
+ # tick! picked this entry under the mutex but runs here without it: if
162
+ # the entry was released meanwhile (a read-ahead claim handed back with
163
+ # vt: 0, a job that finished), extending it would undo that.
164
+ return unless registered?(entry)
165
+
135
166
  vt = config.visibility_timeout
136
167
  entry.client.set_visibility_timeout(entry.queue_name, entry.msg_id, vt: vt, prefixed: entry.prefixed)
137
168
  entry.extended_at = now
@@ -218,6 +249,10 @@ module Pgbus
218
249
  @entries ||= {}
219
250
  end
220
251
 
252
+ def registered?(entry)
253
+ synchronize { entries[key_for(entry)].equal?(entry) }
254
+ end
255
+
221
256
  def key_for(entry)
222
257
  [entry.queue_name, entry.msg_id]
223
258
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.7
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -343,9 +343,12 @@ files:
343
343
  - lib/pgbus/outbox.rb
344
344
  - lib/pgbus/outbox/poller.rb
345
345
  - lib/pgbus/pgmq_schema.rb
346
+ - lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.0.sql
346
347
  - lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql
347
348
  - lib/pgbus/pgmq_schema/pgmq_v1.11.1.sql
348
349
  - lib/pgbus/pgmq_schema/pgmq_v1.12.0.sql
350
+ - lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
351
+ - lib/pgbus/process/claim_buffer.rb
349
352
  - lib/pgbus/process/consumer.rb
350
353
  - lib/pgbus/process/consumer_priority.rb
351
354
  - lib/pgbus/process/dispatcher.rb
@@ -374,6 +377,7 @@ files:
374
377
  - lib/pgbus/recurring/scheduler.rb
375
378
  - lib/pgbus/recurring/task.rb
376
379
  - lib/pgbus/retry_backoff.rb
380
+ - lib/pgbus/ruby_jit.rb
377
381
  - lib/pgbus/serializer.rb
378
382
  - lib/pgbus/stat_buffer.rb
379
383
  - lib/pgbus/streams.rb