pgbus 0.21.3 → 0.22.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/README.md +1 -1
- data/Rakefile +8 -1
- data/lib/pgbus/active_job/executor.rb +58 -29
- data/lib/pgbus/batch/sweep.rb +2 -0
- data/lib/pgbus/batch.rb +4 -1
- data/lib/pgbus/client/native_statements.rb +420 -0
- data/lib/pgbus/client.rb +15 -7
- data/lib/pgbus/concurrency/blocked_execution.rb +1 -0
- data/lib/pgbus/configuration.rb +87 -0
- data/lib/pgbus/event.rb +10 -0
- data/lib/pgbus/event_bus/handler.rb +2 -0
- data/lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.1.sql +265 -0
- data/lib/pgbus/pgmq_schema/pgmq_v1.13.1.sql +2324 -0
- data/lib/pgbus/pgmq_schema.rb +34 -4
- data/lib/pgbus/process/completer.rb +187 -0
- data/lib/pgbus/process/consumer.rb +15 -1
- data/lib/pgbus/process/worker.rb +29 -1
- data/lib/pgbus/serializer.rb +1 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/visibility_heartbeat.rb +16 -12
- data/sig/pgbus/client.rbs +1 -0
- data/sig/pgbus/configuration.rbs +8 -0
- data/sig/pgbus/process/completer.rbs +26 -0
- metadata +6 -1
data/lib/pgbus/pgmq_schema.rb
CHANGED
|
@@ -16,6 +16,23 @@ module Pgbus
|
|
|
16
16
|
SCHEMA_DIR = File.expand_path("pgmq_schema", __dir__).freeze
|
|
17
17
|
FIXUPS_DIR = File.join(SCHEMA_DIR, "fixups").freeze
|
|
18
18
|
|
|
19
|
+
# Wrapped around the vendored SQL by install_sql; see there.
|
|
20
|
+
SAVE_SEARCH_PATH_SQL = <<~SQL
|
|
21
|
+
DO $pgbus_save_path$
|
|
22
|
+
BEGIN
|
|
23
|
+
PERFORM pg_catalog.set_config('pgbus.install_saved_search_path', pg_catalog.current_setting('search_path'), true);
|
|
24
|
+
END
|
|
25
|
+
$pgbus_save_path$;
|
|
26
|
+
SQL
|
|
27
|
+
|
|
28
|
+
RESTORE_SEARCH_PATH_SQL = <<~SQL
|
|
29
|
+
DO $pgbus_restore_path$
|
|
30
|
+
BEGIN
|
|
31
|
+
PERFORM pg_catalog.set_config('search_path', pg_catalog.current_setting('pgbus.install_saved_search_path'), true);
|
|
32
|
+
END
|
|
33
|
+
$pgbus_restore_path$;
|
|
34
|
+
SQL
|
|
35
|
+
|
|
19
36
|
class << self
|
|
20
37
|
# Returns the latest vendored PGMQ version string.
|
|
21
38
|
def latest_version
|
|
@@ -78,11 +95,21 @@ module Pgbus
|
|
|
78
95
|
# Strips the extension-only pg_dump config blocks since they're
|
|
79
96
|
# irrelevant when not installed as an extension.
|
|
80
97
|
#
|
|
98
|
+
# From 1.13.1 upstream pins `search_path = pg_catalog, pg_temp` for the
|
|
99
|
+
# install with a transaction-local set_config, which outlives the install
|
|
100
|
+
# inside a migration (or a caller's transaction, in the client's lazy
|
|
101
|
+
# install): the next unqualified CREATE TABLE would target pg_catalog.
|
|
102
|
+
# The caller's path is saved before upstream's text and restored after,
|
|
103
|
+
# both transaction-local, so the install's pinning still applies to its
|
|
104
|
+
# own statements and nothing after them. Run the result as one string:
|
|
105
|
+
# a statement-by-statement load would lose the saved path between
|
|
106
|
+
# statements.
|
|
107
|
+
#
|
|
81
108
|
# @param version [String] defaults to latest
|
|
82
109
|
# @return [String] SQL
|
|
83
110
|
def install_sql(version = latest_version)
|
|
84
|
-
sql = sql_for_version(version)
|
|
85
|
-
|
|
111
|
+
sql = strip_extension_only_blocks(sql_for_version(version))
|
|
112
|
+
"#{SAVE_SEARCH_PATH_SQL}\n#{sql}\n#{RESTORE_SEARCH_PATH_SQL}"
|
|
86
113
|
end
|
|
87
114
|
|
|
88
115
|
# Returns SQL to create the version tracking table and record an installation.
|
|
@@ -231,9 +258,12 @@ module Pgbus
|
|
|
231
258
|
""
|
|
232
259
|
)
|
|
233
260
|
|
|
234
|
-
# Remove _belongs_to_pgmq function (checks pg_depend on extension)
|
|
261
|
+
# Remove _belongs_to_pgmq function (checks pg_depend on extension).
|
|
262
|
+
# From 1.13.1 its terminator carries `SET search_path = ...`; ending
|
|
263
|
+
# the match at a bare `LANGUAGE plpgsql;` would run on to the next
|
|
264
|
+
# function without that clause and strip everything in between.
|
|
235
265
|
sql.gsub(
|
|
236
|
-
/CREATE FUNCTION pgmq\._belongs_to_pgmq
|
|
266
|
+
/CREATE FUNCTION pgmq\._belongs_to_pgmq.*?\$\$\s*LANGUAGE plpgsql[^;]*;/m,
|
|
237
267
|
""
|
|
238
268
|
)
|
|
239
269
|
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
module Process
|
|
5
|
+
# Batched completion (issue #606, `config.completion_batching`): one per
|
|
6
|
+
# worker, it archives the messages of jobs that finish close together with
|
|
7
|
+
# one `archive_batch` per physical queue instead of one `pgmq.archive` each.
|
|
8
|
+
#
|
|
9
|
+
# There is no timer. An idle completer flushes a lone id at once; ids that
|
|
10
|
+
# arrive while a flush is in flight wait for the next one, so under load a
|
|
11
|
+
# batch is "what arrived during the previous round trip".
|
|
12
|
+
# `batch_size` only caps the ids per statement.
|
|
13
|
+
#
|
|
14
|
+
# The job thread waits for its own result (`complete` blocks), so the
|
|
15
|
+
# executor's post-archive sequence runs on it as before. Threads only: a
|
|
16
|
+
# blocking wait would stall an Async reactor (Configuration#validate!).
|
|
17
|
+
#
|
|
18
|
+
# completed_jobs: :delete (the config setting, issue #607) flushes with
|
|
19
|
+
# `delete_batch` instead; the results keep their names, so :archived
|
|
20
|
+
# means "completed" and :already_archived "already gone".
|
|
21
|
+
class Completer
|
|
22
|
+
# Raised on a waiting job thread whose flush can no longer happen: the
|
|
23
|
+
# flusher was stopped past its join budget, or died.
|
|
24
|
+
class Stopped < Pgbus::Error; end
|
|
25
|
+
|
|
26
|
+
# One job thread's wait for its id's result.
|
|
27
|
+
class Waiter
|
|
28
|
+
def initialize
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
@done = ConditionVariable.new
|
|
31
|
+
@state = nil
|
|
32
|
+
@value = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve(value) = settle(:value, value)
|
|
36
|
+
|
|
37
|
+
def fail(error) = settle(:error, error)
|
|
38
|
+
|
|
39
|
+
def value
|
|
40
|
+
@mutex.synchronize do
|
|
41
|
+
@done.wait(@mutex) until @state
|
|
42
|
+
raise @value if @state == :error
|
|
43
|
+
|
|
44
|
+
@value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# First settle wins: a waiter already served is not failed again by
|
|
51
|
+
# the flusher's last-resort cleanup.
|
|
52
|
+
def settle(state, value)
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
next if @state
|
|
55
|
+
|
|
56
|
+
@state = state
|
|
57
|
+
@value = value
|
|
58
|
+
@done.broadcast
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
BATCH_OPS = { archive: :archive_batch, delete: :delete_batch }.freeze
|
|
64
|
+
|
|
65
|
+
def initialize(client:, batch_size:, completed_jobs: :archive)
|
|
66
|
+
@batch_op = BATCH_OPS.fetch(completed_jobs) do
|
|
67
|
+
raise ArgumentError, "completed_jobs must be :archive or :delete (got #{completed_jobs.inspect})"
|
|
68
|
+
end
|
|
69
|
+
@client = client
|
|
70
|
+
@batch_size = batch_size
|
|
71
|
+
@mutex = Mutex.new
|
|
72
|
+
@wake = ConditionVariable.new
|
|
73
|
+
@pending = {}
|
|
74
|
+
@taken = nil
|
|
75
|
+
@stopping = false
|
|
76
|
+
# Owned by this completer: one per worker, built with it. #stop asks
|
|
77
|
+
# it to drain and exit, then joins it (Worker#shutdown, #immediate_shutdown).
|
|
78
|
+
@thread = Thread.new { run } # rubocop:disable ThreadSafety/NewThread
|
|
79
|
+
@thread.name = "pgbus-completer"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Archives msg_id from the physical (already prefixed) queue and returns
|
|
83
|
+
# :archived, or :already_archived when the message was gone (another
|
|
84
|
+
# worker archived it). Raises the flush's exception on this thread.
|
|
85
|
+
def complete(queue, msg_id)
|
|
86
|
+
waiter = @mutex.synchronize do
|
|
87
|
+
next if @stopping
|
|
88
|
+
|
|
89
|
+
entry = Waiter.new
|
|
90
|
+
(@pending[queue] ||= []) << [msg_id, entry]
|
|
91
|
+
@wake.signal
|
|
92
|
+
entry
|
|
93
|
+
end
|
|
94
|
+
return waiter.value if waiter
|
|
95
|
+
|
|
96
|
+
# Stopped: complete on the caller's thread, with the same results.
|
|
97
|
+
@client.public_send(@batch_op, queue, [msg_id], prefixed: false).empty? ? :already_archived : :archived
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Flushes whatever is pending, then joins the flusher. Past timeout the
|
|
101
|
+
# flusher is killed and every waiter it had not served raises Stopped.
|
|
102
|
+
# Later `complete` calls archive on their own thread. Idempotent.
|
|
103
|
+
def stop(timeout: nil)
|
|
104
|
+
@mutex.synchronize do
|
|
105
|
+
@stopping = true
|
|
106
|
+
@wake.signal
|
|
107
|
+
end
|
|
108
|
+
return if @thread.join(timeout)
|
|
109
|
+
|
|
110
|
+
@thread.kill
|
|
111
|
+
@thread.join
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def alive? = @thread.alive?
|
|
115
|
+
|
|
116
|
+
# Ids waiting for the next flush (test and introspection seam).
|
|
117
|
+
def pending_count
|
|
118
|
+
@mutex.synchronize { @pending.sum { |_queue, entries| entries.size } }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# True while a flush is running (test and introspection seam).
|
|
122
|
+
def flushing?
|
|
123
|
+
@mutex.synchronize { !@taken.nil? }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def run
|
|
129
|
+
while (batch = take)
|
|
130
|
+
flush(batch)
|
|
131
|
+
@mutex.synchronize { @taken = nil }
|
|
132
|
+
end
|
|
133
|
+
ensure
|
|
134
|
+
fail_unserved
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Waits for pending ids and takes all of them; nil once stopping with
|
|
138
|
+
# nothing left.
|
|
139
|
+
def take
|
|
140
|
+
@mutex.synchronize do
|
|
141
|
+
@wake.wait(@mutex) while @pending.empty? && !@stopping
|
|
142
|
+
next if @pending.empty?
|
|
143
|
+
|
|
144
|
+
@taken = @pending
|
|
145
|
+
@pending = {}
|
|
146
|
+
@taken
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def flush(batch)
|
|
151
|
+
batch.each do |queue, entries|
|
|
152
|
+
entries.each_slice(@batch_size) { |slice| flush_slice(queue, slice) }
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# pgmq returns one row per archived id (as a String); an id already gone
|
|
157
|
+
# is absent. A duplicated id (the same message held by two threads after
|
|
158
|
+
# a redelivery) is archived once, so only its first waiter gets :archived.
|
|
159
|
+
def flush_slice(queue, slice)
|
|
160
|
+
archived = @client.public_send(@batch_op, queue, slice.map(&:first).uniq, prefixed: false).to_set(&:to_i)
|
|
161
|
+
slice.each { |msg_id, waiter| waiter.resolve(archived.delete?(msg_id) ? :archived : :already_archived) }
|
|
162
|
+
rescue Exception => e # rubocop:disable Lint/RescueException -- delivered to the waiters, never swallowed
|
|
163
|
+
log_flush_failure(queue, slice, e) unless e.is_a?(StandardError)
|
|
164
|
+
slice.map(&:last).each { |waiter| waiter.fail(e) }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def log_flush_failure(queue, slice, error)
|
|
168
|
+
Pgbus.logger.error do
|
|
169
|
+
"[Pgbus::Completer] #{@batch_op} on #{queue} (#{slice.size} ids) raised #{error.class}: #{error.message}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# The flusher is exiting: nothing may be left waiting on it.
|
|
174
|
+
def fail_unserved
|
|
175
|
+
leftovers = @mutex.synchronize do
|
|
176
|
+
entries = [@taken, @pending].compact.flat_map(&:values).flatten(1)
|
|
177
|
+
@taken = nil
|
|
178
|
+
@pending = {}
|
|
179
|
+
@stopping = true
|
|
180
|
+
entries
|
|
181
|
+
end
|
|
182
|
+
error = Stopped.new("the completer stopped before archiving this message")
|
|
183
|
+
leftovers.map(&:last).each { |waiter| waiter.fail(error) }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -264,7 +264,7 @@ module Pgbus
|
|
|
264
264
|
dispatch(handlers, message, queue_name)
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
archive_handled(queue_name, message)
|
|
268
268
|
# A redelivery that succeeds leaves no stale failure behind. First
|
|
269
269
|
# deliveries never had one: no query on the hot path (#484's gate).
|
|
270
270
|
if message.read_ct.to_i > 1
|
|
@@ -367,6 +367,20 @@ module Pgbus
|
|
|
367
367
|
# as retrying with its error. No ErrorReporter call: Handler#process!
|
|
368
368
|
# already instruments pgbus.event_failed, which feeds the reporters.
|
|
369
369
|
# A handler queue has no priority sub-queues: its one table is the source.
|
|
370
|
+
# The handlers ran. An archive whose reply was lost after it may have
|
|
371
|
+
# committed, and whose re-run found the message gone (issue #607
|
|
372
|
+
# review), leaves nothing to retry: the message is handled.
|
|
373
|
+
def archive_handled(queue_name, message)
|
|
374
|
+
Pgbus.client.archive_message(queue_name, message.msg_id.to_i)
|
|
375
|
+
rescue StandardError => e # pgmq loads lazily: completion_lost? checks the class
|
|
376
|
+
raise unless Client::NativeStatements.completion_lost?(e)
|
|
377
|
+
|
|
378
|
+
Pgbus.logger.warn do
|
|
379
|
+
"[Pgbus] Consumer archive of #{message.msg_id} on #{queue_name} lost its reply and the message is gone: " \
|
|
380
|
+
"counted as handled (#{e.message})"
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
370
384
|
def record_failed_event(message, queue_name, error)
|
|
371
385
|
FailedEventRecorder.record!(
|
|
372
386
|
queue_name: queue_name, msg_id: message.msg_id.to_i, source_queue: config.queue_name(queue_name),
|
data/lib/pgbus/process/worker.rb
CHANGED
|
@@ -62,6 +62,12 @@ module Pgbus
|
|
|
62
62
|
"fair_share and group_mode are mutually exclusive — this worker has group_mode " \
|
|
63
63
|
"#{(@group_mode || config.group_mode).inspect} while config.fair_share is set"
|
|
64
64
|
end
|
|
65
|
+
# The completer blocks a job thread on its flush, which would stall an
|
|
66
|
+
# Async reactor (issue #606); the mode can arrive here without validate!.
|
|
67
|
+
if config.completion_batching && @execution_mode == :async
|
|
68
|
+
raise Pgbus::ConfigurationError,
|
|
69
|
+
"completion_batching supports execution_mode :threads only; this worker runs :async"
|
|
70
|
+
end
|
|
65
71
|
@single_active_consumer = single_active_consumer
|
|
66
72
|
@consumer_priority = consumer_priority
|
|
67
73
|
@lifecycle = Lifecycle.new
|
|
@@ -94,7 +100,16 @@ module Pgbus
|
|
|
94
100
|
else
|
|
95
101
|
stat_buffer
|
|
96
102
|
end
|
|
97
|
-
|
|
103
|
+
# One per worker when config.completion_batching (issue #606): archives
|
|
104
|
+
# the jobs that finish close together in one archive_batch, or deletes
|
|
105
|
+
# them with delete_batch under config.completed_jobs = :delete (#607).
|
|
106
|
+
@completer = if config.completion_batching
|
|
107
|
+
Completer.new(client: Pgbus.client, batch_size: config.completion_batch_size,
|
|
108
|
+
completed_jobs: config.completed_jobs)
|
|
109
|
+
end
|
|
110
|
+
# The worker's config, so the executor and the completer read one
|
|
111
|
+
# completed_jobs (issue #607).
|
|
112
|
+
@executor = Pgbus::ActiveJob::Executor.new(config: config, stat_buffer: @stat_buffer, completer: @completer)
|
|
98
113
|
@wake_signal = wake_signal || WakeSignal.new
|
|
99
114
|
@pool = ExecutionPools.build(
|
|
100
115
|
mode: @execution_mode,
|
|
@@ -239,6 +254,13 @@ module Pgbus
|
|
|
239
254
|
Pgbus.stopping = true
|
|
240
255
|
@lifecycle.transition_to!(:stopped)
|
|
241
256
|
@wake_signal.notify!
|
|
257
|
+
# Before the pool: a job thread killed while it waits on a flush would
|
|
258
|
+
# skip its signals while the flush still commits (archived, never
|
|
259
|
+
# signalled). Stopping first resolves every waiter; a job that finishes
|
|
260
|
+
# inside the grace then archives on its own thread. Bounded by the same
|
|
261
|
+
# grace: a flush wedged on a hung Postgres is abandoned, and its jobs
|
|
262
|
+
# fail with Completer::Stopped and re-run after visibility_timeout.
|
|
263
|
+
@completer&.stop(timeout: config.immediate_shutdown_grace)
|
|
242
264
|
ExecutionPools.terminate(@pool, grace: config.immediate_shutdown_grace)
|
|
243
265
|
end
|
|
244
266
|
|
|
@@ -927,7 +949,13 @@ module Pgbus
|
|
|
927
949
|
# config.drain_timeout for in-flight jobs. A job still running here has
|
|
928
950
|
# proven it won't finish; waiting another full window would push the
|
|
929
951
|
# worker past the supervisor's shutdown_timeout deadline (issue #386).
|
|
952
|
+
residual_deadline = monotonic_now + POOL_TERMINATION_WAIT
|
|
930
953
|
@pool.wait_for_termination(POOL_TERMINATION_WAIT)
|
|
954
|
+
# After the pool: every waiter is a job thread, so the drain already
|
|
955
|
+
# flushed theirs; this flushes any left and joins the flusher. It shares
|
|
956
|
+
# the pool's residual budget rather than adding its own, so shutdown
|
|
957
|
+
# stays inside the supervisor's drain_timeout + 5 default deadline.
|
|
958
|
+
@completer&.stop(timeout: [residual_deadline - monotonic_now, 0].max)
|
|
931
959
|
VisibilityHeartbeat.stop
|
|
932
960
|
@stat_buffer&.stop
|
|
933
961
|
@queue_lock&.unlock_all
|
data/lib/pgbus/serializer.rb
CHANGED
data/lib/pgbus/version.rb
CHANGED
|
@@ -33,8 +33,13 @@ module Pgbus
|
|
|
33
33
|
# the whole table is bounded by the execution pool's capacity plus the
|
|
34
34
|
# read-ahead buffer (threads + read_ahead per process, issue #486) — a
|
|
35
35
|
# handful of entries per process, not one per enqueued job.
|
|
36
|
+
#
|
|
37
|
+
# `key` is the entry's @entries key, [queue_name, msg_id], built once with
|
|
38
|
+
# the entry (#track, #hold): #register, #unregister and #registered? each
|
|
39
|
+
# used to build their own (issue #604). A tenth member stays in the
|
|
40
|
+
# 160-byte slot.
|
|
36
41
|
Entry = Struct.new(:client, :queue_name, :prefixed, :msg_id, :job_class, :extended_at, :extensions,
|
|
37
|
-
:concurrency, :on_beat, keyword_init: true)
|
|
42
|
+
:concurrency, :on_beat, :key, keyword_init: true)
|
|
38
43
|
|
|
39
44
|
# Per-job opt-out, included on ActiveJob::Base by the engine:
|
|
40
45
|
#
|
|
@@ -81,9 +86,10 @@ module Pgbus
|
|
|
81
86
|
concurrency: nil, on_beat: nil)
|
|
82
87
|
return yield unless config.visibility_heartbeat
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
msg_id = msg_id.to_i
|
|
90
|
+
entry = Entry.new(client: client, queue_name: queue_name, prefixed: prefixed, msg_id: msg_id,
|
|
85
91
|
job_class: job_class, extended_at: monotonic_now, extensions: 0,
|
|
86
|
-
concurrency: concurrency, on_beat: on_beat)
|
|
92
|
+
concurrency: concurrency, on_beat: on_beat, key: [queue_name, msg_id].freeze)
|
|
87
93
|
register(entry, config)
|
|
88
94
|
begin
|
|
89
95
|
yield
|
|
@@ -100,8 +106,10 @@ module Pgbus
|
|
|
100
106
|
def hold(client:, queue_name:, msg_id:, prefixed: true, job_class: nil, config: Pgbus.configuration)
|
|
101
107
|
return unless config.visibility_heartbeat
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
msg_id = msg_id.to_i
|
|
110
|
+
entry = Entry.new(client: client, queue_name: queue_name, prefixed: prefixed, msg_id: msg_id,
|
|
111
|
+
job_class: job_class, extended_at: monotonic_now, extensions: 0,
|
|
112
|
+
key: [queue_name, msg_id].freeze)
|
|
105
113
|
register(entry, config)
|
|
106
114
|
entry
|
|
107
115
|
end
|
|
@@ -159,7 +167,7 @@ module Pgbus
|
|
|
159
167
|
@running = false
|
|
160
168
|
@thread = nil
|
|
161
169
|
end
|
|
162
|
-
@entries[
|
|
170
|
+
@entries[entry.key] = entry
|
|
163
171
|
# Start the ticker once per process.
|
|
164
172
|
unless @running && @thread&.alive?
|
|
165
173
|
@running = true
|
|
@@ -173,7 +181,7 @@ module Pgbus
|
|
|
173
181
|
# job's entry (issue #486).
|
|
174
182
|
def unregister(entry)
|
|
175
183
|
synchronize do
|
|
176
|
-
key =
|
|
184
|
+
key = entry.key
|
|
177
185
|
@entries.delete(key) if @entries[key].equal?(entry)
|
|
178
186
|
end
|
|
179
187
|
end
|
|
@@ -265,11 +273,7 @@ module Pgbus
|
|
|
265
273
|
end
|
|
266
274
|
|
|
267
275
|
def registered?(entry)
|
|
268
|
-
synchronize { @entries[
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def key_for(entry)
|
|
272
|
-
[entry.queue_name, entry.msg_id]
|
|
276
|
+
synchronize { @entries[entry.key].equal?(entry) }
|
|
273
277
|
end
|
|
274
278
|
|
|
275
279
|
# A literal block, not `(&)`: ThreadSafety/ClassInstanceVariable accepts
|
data/sig/pgbus/client.rbs
CHANGED
|
@@ -33,6 +33,7 @@ module Pgbus
|
|
|
33
33
|
def message_with_job_id?: (untyped queue_name, job_id: untyped, ?prefixed: untyped) -> bool?
|
|
34
34
|
def metrics: (?untyped queue_name) -> untyped
|
|
35
35
|
def move_to_dead_letter: (untyped queue_name, untyped message, ?headers: untyped) -> untyped
|
|
36
|
+
def native_ready?: (String full_name) -> bool
|
|
36
37
|
def notify_enabled?: (untyped queue_name) -> bool
|
|
37
38
|
def notify_stream: (untyped stream_name, untyped payload) -> untyped
|
|
38
39
|
def oldest_claimable_ages: (?untyped queue_name) -> untyped
|
data/sig/pgbus/configuration.rbs
CHANGED
|
@@ -30,6 +30,12 @@ module Pgbus
|
|
|
30
30
|
def capsule_named: (untyped name) -> untyped
|
|
31
31
|
def circuit_breaker_enabled: () -> bool
|
|
32
32
|
def circuit_breaker_enabled=: (untyped) -> untyped
|
|
33
|
+
def completed_jobs: () -> (:archive | :delete)
|
|
34
|
+
def completed_jobs=: (untyped value) -> (:archive | :delete)
|
|
35
|
+
def completion_batch_size: () -> Integer
|
|
36
|
+
def completion_batch_size=: (untyped) -> untyped
|
|
37
|
+
def completion_batching: () -> bool
|
|
38
|
+
def completion_batching=: (untyped) -> untyped
|
|
33
39
|
def connection_guc_mode: () -> Symbol
|
|
34
40
|
def connection_guc_mode=: (untyped mode) -> untyped
|
|
35
41
|
def connection_options: () -> untyped
|
|
@@ -114,6 +120,8 @@ module Pgbus
|
|
|
114
120
|
def metrics_backend=: (untyped) -> untyped
|
|
115
121
|
def metrics_enabled: () -> bool
|
|
116
122
|
def metrics_enabled=: (untyped) -> untyped
|
|
123
|
+
def native_statements: () -> bool
|
|
124
|
+
def native_statements=: (bool value) -> bool
|
|
117
125
|
def notify_presence_change: (untyped stream_name, untyped change) -> untyped
|
|
118
126
|
def outbox_batch_size: () -> Integer
|
|
119
127
|
def outbox_batch_size=: (untyped) -> untyped
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Pgbus
|
|
2
|
+
module Process
|
|
3
|
+
# Batched completion (issue #606): one per worker when
|
|
4
|
+
# config.completion_batching is on.
|
|
5
|
+
class Completer
|
|
6
|
+
class Stopped < Pgbus::Error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Waiter
|
|
10
|
+
def initialize: () -> void
|
|
11
|
+
def resolve: (Symbol value) -> void
|
|
12
|
+
def fail: (Exception error) -> void
|
|
13
|
+
def value: () -> Symbol
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
BATCH_OPS: Hash[Symbol, Symbol]
|
|
17
|
+
|
|
18
|
+
def initialize: (client: Pgbus::Client, batch_size: Integer, ?completed_jobs: :archive | :delete) -> void
|
|
19
|
+
def complete: (String queue, Integer msg_id) -> (:archived | :already_archived)
|
|
20
|
+
def stop: (?timeout: Numeric?) -> void
|
|
21
|
+
def alive?: () -> bool
|
|
22
|
+
def pending_count: () -> Integer
|
|
23
|
+
def flushing?: () -> bool
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
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.
|
|
4
|
+
version: 0.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -356,6 +356,7 @@ files:
|
|
|
356
356
|
- lib/pgbus/client/connection_health.rb
|
|
357
357
|
- lib/pgbus/client/ensure_stream_queue.rb
|
|
358
358
|
- lib/pgbus/client/fair_read.rb
|
|
359
|
+
- lib/pgbus/client/native_statements.rb
|
|
359
360
|
- lib/pgbus/client/notify_lock_retry.rb
|
|
360
361
|
- lib/pgbus/client/notify_stream.rb
|
|
361
362
|
- lib/pgbus/client/read_after.rb
|
|
@@ -456,11 +457,14 @@ files:
|
|
|
456
457
|
- lib/pgbus/outbox/poller.rb
|
|
457
458
|
- lib/pgbus/pgmq_schema.rb
|
|
458
459
|
- lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.0.sql
|
|
460
|
+
- lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.1.sql
|
|
459
461
|
- lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql
|
|
460
462
|
- lib/pgbus/pgmq_schema/pgmq_v1.11.1.sql
|
|
461
463
|
- lib/pgbus/pgmq_schema/pgmq_v1.12.0.sql
|
|
462
464
|
- lib/pgbus/pgmq_schema/pgmq_v1.13.0.sql
|
|
465
|
+
- lib/pgbus/pgmq_schema/pgmq_v1.13.1.sql
|
|
463
466
|
- lib/pgbus/process/claim_buffer.rb
|
|
467
|
+
- lib/pgbus/process/completer.rb
|
|
464
468
|
- lib/pgbus/process/consumer.rb
|
|
465
469
|
- lib/pgbus/process/consumer_priority.rb
|
|
466
470
|
- lib/pgbus/process/dispatcher.rb
|
|
@@ -614,6 +618,7 @@ files:
|
|
|
614
618
|
- sig/pgbus/client.rbs
|
|
615
619
|
- sig/pgbus/configuration.rbs
|
|
616
620
|
- sig/pgbus/configuration/snapshot.rbs
|
|
621
|
+
- sig/pgbus/process/completer.rbs
|
|
617
622
|
- sig/pgbus/streams.rbs
|
|
618
623
|
- sig/pgbus/streams/stream.rbs
|
|
619
624
|
homepage: https://github.com/zoolutions/pgbus
|