pgbus 0.23.1 → 0.23.2

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.
@@ -58,28 +58,30 @@ module Pgbus
58
58
  def promote_next(concurrency_key, client:, delay: 0)
59
59
  released = nil
60
60
  msg_id = nil
61
- Pgbus::BlockedExecution.transaction(requires_new: true) do
62
- released = release_next(concurrency_key)
63
- raise ActiveRecord::Rollback unless released
64
-
65
- # Resolve the delay first: a parked scheduled job goes back into
66
- # PGMQ invisible, and nothing renews its lease until a worker
67
- # picks it up, so the wait has to be part of the lease.
68
- actual_delay = resolve_delay(released[:payload], delay)
69
- raise ActiveRecord::Rollback unless slot_taken?(concurrency_key, released[:payload], actual_delay)
70
-
71
- # Carry the enqueuer's priority through: under priority routing it
72
- # picks the _pN sub-queue, not just the release order (issue #423).
73
- headers = released[:payload].is_a?(Hash) ? released[:payload].delete(HEADERS_KEY) : nil
74
- msg_id = client.send_message(released[:queue_name], released[:payload],
75
- delay: actual_delay, priority: released[:priority],
76
- **(headers ? { headers: headers } : {}))
61
+ Pgbus::BlockedExecution.connection_pool.with_connection do
62
+ Pgbus::BlockedExecution.transaction(requires_new: true) do
63
+ released = release_next(concurrency_key)
64
+ raise ActiveRecord::Rollback unless released
65
+
66
+ # Resolve the delay first: a parked scheduled job goes back into
67
+ # PGMQ invisible, and nothing renews its lease until a worker
68
+ # picks it up, so the wait has to be part of the lease.
69
+ actual_delay = resolve_delay(released[:payload], delay)
70
+ raise ActiveRecord::Rollback unless slot_taken?(concurrency_key, released[:payload], actual_delay)
71
+
72
+ # Carry the enqueuer's priority through: under priority routing it
73
+ # picks the _pN sub-queue, not just the release order (issue #423).
74
+ headers = released[:payload].is_a?(Hash) ? released[:payload].delete(HEADERS_KEY) : nil
75
+ msg_id = client.send_message(released[:queue_name], released[:payload],
76
+ delay: actual_delay, priority: released[:priority],
77
+ **(headers ? { headers: headers } : {}))
78
+ end
79
+
80
+ next false unless released && msg_id
81
+
82
+ backfill(released, msg_id, client)
83
+ true
77
84
  end
78
-
79
- return false unless released && msg_id
80
-
81
- backfill(released, msg_id, client)
82
- true
83
85
  rescue StandardError => e
84
86
  Pgbus.logger.warn { "[Pgbus] Promote blocked execution failed for #{concurrency_key}: #{e.message}" }
85
87
  false
@@ -112,9 +114,11 @@ module Pgbus
112
114
  # un-taking the slot while the message is already live, so the job
113
115
  # runs a second time.
114
116
  def backfill(released, msg_id, client)
115
- Pgbus::BlockedExecution.transaction(requires_new: true) do
116
- Batch.backfill_execution(released[:payload], msg_id,
117
- client.target_queue(released[:queue_name], released[:priority]))
117
+ Pgbus::BlockedExecution.connection_pool.with_connection do
118
+ Pgbus::BlockedExecution.transaction(requires_new: true) do
119
+ Batch.backfill_execution(released[:payload], msg_id,
120
+ client.target_queue(released[:queue_name], released[:priority]))
121
+ end
118
122
  end
119
123
  rescue StandardError => e
120
124
  Pgbus.logger.warn { "[Pgbus] Batch execution backfill failed after promote: #{e.message}" }
@@ -19,17 +19,23 @@ module Pgbus
19
19
 
20
20
  # Release one slot in the semaphore. Called after a job completes.
21
21
  def release(key)
22
- Pgbus::Semaphore.where(key: key).update_all("value = GREATEST(value - 1, 0)")
22
+ Pgbus::Semaphore.connection_pool.with_connection do
23
+ Pgbus::Semaphore.where(key: key).update_all("value = GREATEST(value - 1, 0)")
24
+ end
23
25
  end
24
26
 
25
27
  # A job holding a slot is done: give the slot back and hand it to the
26
28
  # next parked job for this key, in one transaction. The decrement locks
27
29
  # the semaphore row first, so an enqueue that is parking a job right
28
30
  # now commits before the promote looks for parked rows.
31
+ # The lease is the caller's when it holds one, else taken for the
32
+ # signal and returned at its end (issue #649).
29
33
  def signal(key, client:)
30
- Pgbus::Semaphore.transaction do
31
- release(key)
32
- BlockedExecution.promote_next(key, client: client)
34
+ Pgbus::Semaphore.connection_pool.with_connection do
35
+ Pgbus::Semaphore.transaction do
36
+ release(key)
37
+ BlockedExecution.promote_next(key, client: client)
38
+ end
33
39
  end
34
40
  end
35
41
 
@@ -54,11 +60,13 @@ module Pgbus
54
60
  # Returns an array of hashes with expired keys.
55
61
  # Uses DELETE ... RETURNING for atomicity (no race between pluck and delete).
56
62
  def expire_stale
57
- result = Pgbus::Semaphore.connection.exec_query(
58
- "DELETE FROM pgbus_semaphores WHERE expires_at < $1 RETURNING key",
59
- "Pgbus Semaphore Expire",
60
- [Time.current]
61
- )
63
+ result = Pgbus::Semaphore.connection_pool.with_connection do |conn|
64
+ conn.exec_query(
65
+ "DELETE FROM pgbus_semaphores WHERE expires_at < $1 RETURNING key",
66
+ "Pgbus Semaphore Expire",
67
+ [Time.current]
68
+ )
69
+ end
62
70
  result.rows.map { |row| { "key" => row[0] } }
63
71
  end
64
72
 
@@ -32,11 +32,12 @@ module Pgbus
32
32
  retry_count
33
33
  ]
34
34
  legacy = !source_queue_column?
35
- if legacy
36
- connection.exec_query(LEGACY_RECORD_SQL, "FailedEvent Record", [queue_name, msg_id.to_i, *details])
37
- else
38
- connection.exec_query(RECORD_SQL, "FailedEvent Record", [queue_name, source_queue, msg_id.to_i, *details])
39
- end
35
+ sql, binds = if legacy
36
+ [LEGACY_RECORD_SQL, [queue_name, msg_id.to_i, *details]]
37
+ else
38
+ [RECORD_SQL, [queue_name, source_queue, msg_id.to_i, *details]]
39
+ end
40
+ with_connection { |conn| conn.exec_query(sql, "FailedEvent Record", binds) }
40
41
  rescue StandardError => e
41
42
  # A process started before the migration would keep sending the old
42
43
  # ON CONFLICT target, whose index the migration drops: probe again.
@@ -46,7 +47,9 @@ module Pgbus
46
47
 
47
48
  def exists?(queue_name:, msg_id:, source_queue:)
48
49
  sql, binds = message_scope(queue_name, msg_id, source_queue)
49
- result = connection.select_value("SELECT 1 FROM pgbus_failed_events WHERE #{sql} LIMIT 1", "FailedEvent Exists", binds)
50
+ result = with_connection do |conn|
51
+ conn.select_value("SELECT 1 FROM pgbus_failed_events WHERE #{sql} LIMIT 1", "FailedEvent Exists", binds)
52
+ end
50
53
  !result.nil?
51
54
  rescue StandardError => e
52
55
  Pgbus.logger.debug { "[Pgbus] FailedEvent exists? check failed: #{e.class}: #{e.message}" }
@@ -61,12 +64,14 @@ module Pgbus
61
64
  def last_error(queue_name:, msg_id:, source_queue:)
62
65
  sql, binds = message_scope(queue_name, msg_id, source_queue)
63
66
  order = binds.size > 2 ? " ORDER BY source_queue IS NULL, failed_at DESC" : ""
64
- row = connection.select_one(
65
- "SELECT error_class, error_message, backtrace, retry_count, failed_at " \
66
- "FROM pgbus_failed_events WHERE #{sql}#{order} LIMIT 1",
67
- "FailedEvent Last Error",
68
- binds
69
- )
67
+ row = with_connection do |conn|
68
+ conn.select_one(
69
+ "SELECT error_class, error_message, backtrace, retry_count, failed_at " \
70
+ "FROM pgbus_failed_events WHERE #{sql}#{order} LIMIT 1",
71
+ "FailedEvent Last Error",
72
+ binds
73
+ )
74
+ end
70
75
  return unless row
71
76
 
72
77
  {
@@ -89,11 +94,13 @@ module Pgbus
89
94
  # case, while a row may carry the executor's configured spelling
90
95
  # ("Critical") or a physical name from before #633.
91
96
  def clear_queue!(queue_names)
92
- connection.exec_delete(
93
- "DELETE FROM pgbus_failed_events WHERE lower(queue_name) = ANY($1::text[])",
94
- "FailedEvent Clear Queue",
95
- ["{#{queue_names.map(&:downcase).join(",")}}"]
96
- )
97
+ with_connection do |conn|
98
+ conn.exec_delete(
99
+ "DELETE FROM pgbus_failed_events WHERE lower(queue_name) = ANY($1::text[])",
100
+ "FailedEvent Clear Queue",
101
+ ["{#{queue_names.map(&:downcase).join(",")}}"]
102
+ )
103
+ end
97
104
  rescue StandardError => e
98
105
  Pgbus.logger.error do
99
106
  "[Pgbus] Failed to clear failed events for dropped queue(s) #{queue_names.join(", ")}: " \
@@ -105,7 +112,7 @@ module Pgbus
105
112
  # existed: a sibling sub-queue's row with the same msg_id stays.
106
113
  def clear!(queue_name:, msg_id:, source_queue:)
107
114
  sql, binds = message_scope(queue_name, msg_id, source_queue)
108
- connection.exec_delete("DELETE FROM pgbus_failed_events WHERE #{sql}", "FailedEvent Clear", binds)
115
+ with_connection { |conn| conn.exec_delete("DELETE FROM pgbus_failed_events WHERE #{sql}", "FailedEvent Clear", binds) }
109
116
  rescue StandardError => e
110
117
  # ERROR-level: a failed clear leaves a stale row in the dashboard
111
118
  # AFTER the job actually succeeded — confusing and load-bearing
@@ -168,7 +175,7 @@ module Pgbus
168
175
  end
169
176
 
170
177
  def detect_source_queue_column(warn:)
171
- supported = connection.column_exists?(:pgbus_failed_events, :source_queue)
178
+ supported = with_connection { |conn| conn.column_exists?(:pgbus_failed_events, :source_queue) }
172
179
  if warn && !supported
173
180
  Pgbus.logger.warn do
174
181
  "[Pgbus] pgbus_failed_events is missing the source_queue column; under priority routing two " \
@@ -186,12 +193,14 @@ module Pgbus
186
193
  value.to_s
187
194
  end
188
195
 
189
- def connection
190
- if defined?(BusRecord) && BusRecord.connected?
191
- BusRecord.connection
192
- else
193
- ActiveRecord::Base.connection
194
- end
196
+ # A lease the caller already holds is reused and kept; otherwise one is
197
+ # taken for the block and returned at its end (issue #649), so the
198
+ # executor's bookkeeping leaves no connection pinned to the job's
199
+ # thread or fiber. BusRecord's pool is the pgbus database's under
200
+ # connects_to and the primary's otherwise; asking first whether it had
201
+ # connected sent a cold process's first failure to the primary.
202
+ def with_connection(&)
203
+ BusRecord.connection_pool.with_connection(&)
195
204
  end
196
205
  end
197
206
 
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.23.1"
4
+ VERSION = "0.23.2"
5
5
  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.23.1
4
+ version: 0.23.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson