pgbus 0.1.1 → 0.1.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.
- checksums.yaml +4 -4
- data/lib/pgbus/active_job/adapter.rb +3 -3
- data/lib/pgbus/active_job/executor.rb +4 -5
- data/lib/pgbus/batch.rb +5 -3
- data/lib/pgbus/client.rb +7 -8
- data/lib/pgbus/configuration.rb +15 -0
- data/lib/pgbus/event_bus/handler.rb +8 -11
- data/lib/pgbus/process/consumer.rb +9 -0
- data/lib/pgbus/process/dispatcher.rb +12 -22
- data/lib/pgbus/process/supervisor.rb +28 -0
- data/lib/pgbus/process/worker.rb +5 -4
- data/lib/pgbus/recurring/command_job.rb +16 -4
- data/lib/pgbus/serializer.rb +6 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +30 -14
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2aad79af8c595a48d879b9bad0ebaaf43d40843cdbcb9dcc7a2dad0023257e53
|
|
4
|
+
data.tar.gz: 53bda3ba2e7d1d0f1935d183193bff5b48adeca8a4da44b6d55f9ba743e62d8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9cfcd463d6b91591f9f037969ca1b30c2dc29ba0ce7a8eeefa829ef2b12eee71c4aba0f5b8d02b3336d6cc38b04db7cc51d749ca97774d4afa4e2246f8d6ccc
|
|
7
|
+
data.tar.gz: a50591b61b5bf49a2b5e14750622ba7ca0681d3db5f47d801542a23eb3147a8b9d5a605fcd0c55264ad4068e75f3d827ad550d2d58cd2026fa8a4351f72eea8a
|
|
@@ -7,7 +7,7 @@ module Pgbus
|
|
|
7
7
|
class Adapter
|
|
8
8
|
def enqueue(active_job)
|
|
9
9
|
queue = active_job.queue_name || Pgbus.configuration.default_queue
|
|
10
|
-
payload_hash =
|
|
10
|
+
payload_hash = Serializer.serialize_job_hash(active_job)
|
|
11
11
|
payload_hash = Concurrency.inject_metadata(active_job, payload_hash)
|
|
12
12
|
payload_hash = inject_batch_metadata(payload_hash)
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ module Pgbus
|
|
|
16
16
|
|
|
17
17
|
def enqueue_at(active_job, timestamp)
|
|
18
18
|
queue = active_job.queue_name || Pgbus.configuration.default_queue
|
|
19
|
-
payload_hash =
|
|
19
|
+
payload_hash = Serializer.serialize_job_hash(active_job)
|
|
20
20
|
payload_hash = Concurrency.inject_metadata(active_job, payload_hash)
|
|
21
21
|
payload_hash = inject_batch_metadata(payload_hash)
|
|
22
22
|
delay = [(timestamp - Time.now.to_f).ceil, 0].max
|
|
@@ -88,7 +88,7 @@ module Pgbus
|
|
|
88
88
|
def enqueue_immediate(queue, jobs)
|
|
89
89
|
return if jobs.empty?
|
|
90
90
|
|
|
91
|
-
payloads = jobs.map { |j|
|
|
91
|
+
payloads = jobs.map { |j| Serializer.serialize_job_hash(j) }
|
|
92
92
|
msg_ids = Pgbus.client.send_batch(queue, payloads)
|
|
93
93
|
|
|
94
94
|
unless msg_ids.is_a?(Array) && msg_ids.size == jobs.size
|
|
@@ -28,8 +28,8 @@ module Pgbus
|
|
|
28
28
|
Instrumentation.instrument("pgbus.executor.execute", queue: queue_name, job_class: job_class) do
|
|
29
29
|
job = ::ActiveJob::Base.deserialize(payload)
|
|
30
30
|
execute_job(job)
|
|
31
|
-
job_succeeded = true
|
|
32
31
|
client.archive_message(queue_name, message.msg_id.to_i)
|
|
32
|
+
job_succeeded = true
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
instrument("pgbus.job_completed", queue: queue_name, job_class: job_class)
|
|
@@ -41,10 +41,9 @@ module Pgbus
|
|
|
41
41
|
# Semaphore is released only on success or dead-lettering.
|
|
42
42
|
:failed
|
|
43
43
|
ensure
|
|
44
|
-
# Signal concurrency and batch
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# job has already completed successfully.
|
|
44
|
+
# Signal concurrency and batch only when the job was archived successfully.
|
|
45
|
+
# job_succeeded is set AFTER archive_message, so if archive fails the
|
|
46
|
+
# semaphore slot stays held until VT expires and the job is retried.
|
|
48
47
|
if job_succeeded
|
|
49
48
|
signal_concurrency(payload)
|
|
50
49
|
signal_batch_completed(payload)
|
data/lib/pgbus/batch.rb
CHANGED
|
@@ -134,10 +134,12 @@ module Pgbus
|
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
def enqueue_callback(class_name, properties)
|
|
137
|
-
job_class = class_name.
|
|
137
|
+
job_class = class_name.safe_constantize
|
|
138
|
+
unless job_class && job_class < ::ActiveJob::Base
|
|
139
|
+
Pgbus.logger.error { "[Pgbus] Batch callback class invalid or not an ActiveJob: #{class_name}" }
|
|
140
|
+
return
|
|
141
|
+
end
|
|
138
142
|
job_class.perform_later(properties)
|
|
139
|
-
rescue NameError => e
|
|
140
|
-
Pgbus.logger.error { "[Pgbus] Batch callback class not found: #{class_name}: #{e.message}" }
|
|
141
143
|
end
|
|
142
144
|
end
|
|
143
145
|
end
|
data/lib/pgbus/client.rb
CHANGED
|
@@ -24,28 +24,27 @@ module Pgbus
|
|
|
24
24
|
pool_size: config.pool_size,
|
|
25
25
|
pool_timeout: config.pool_timeout
|
|
26
26
|
)
|
|
27
|
-
@queues_created =
|
|
28
|
-
@mutex = Mutex.new
|
|
27
|
+
@queues_created = Concurrent::Map.new
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
def ensure_queue(name)
|
|
32
31
|
full_name = config.queue_name(name)
|
|
33
|
-
@
|
|
34
|
-
return if @queues_created[full_name]
|
|
32
|
+
return if @queues_created[full_name]
|
|
35
33
|
|
|
34
|
+
@queues_created.compute_if_absent(full_name) do
|
|
36
35
|
@pgmq.create(full_name)
|
|
37
36
|
@pgmq.enable_notify_insert(full_name, throttle_interval_ms: config.notify_throttle_ms) if config.listen_notify
|
|
38
|
-
|
|
37
|
+
true
|
|
39
38
|
end
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def ensure_dead_letter_queue(name)
|
|
43
42
|
dlq_name = config.dead_letter_queue_name(name)
|
|
44
|
-
@
|
|
45
|
-
return if @queues_created[dlq_name]
|
|
43
|
+
return if @queues_created[dlq_name]
|
|
46
44
|
|
|
45
|
+
@queues_created.compute_if_absent(dlq_name) do
|
|
47
46
|
@pgmq.create(dlq_name)
|
|
48
|
-
|
|
47
|
+
true
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
data/lib/pgbus/configuration.rb
CHANGED
|
@@ -116,6 +116,21 @@ module Pgbus
|
|
|
116
116
|
@pgmq_schema_mode = mode
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
def validate!
|
|
120
|
+
raise ArgumentError, "pool_size must be > 0" unless pool_size.is_a?(Numeric) && pool_size.positive?
|
|
121
|
+
raise ArgumentError, "pool_timeout must be > 0" unless pool_timeout.is_a?(Numeric) && pool_timeout.positive?
|
|
122
|
+
raise ArgumentError, "polling_interval must be > 0" unless polling_interval.is_a?(Numeric) && polling_interval.positive?
|
|
123
|
+
raise ArgumentError, "visibility_timeout must be > 0" unless visibility_timeout.is_a?(Numeric) && visibility_timeout.positive?
|
|
124
|
+
raise ArgumentError, "max_retries must be >= 0" unless max_retries.is_a?(Integer) && max_retries >= 0
|
|
125
|
+
|
|
126
|
+
workers.each do |w|
|
|
127
|
+
threads = w[:threads] || w["threads"] || 5
|
|
128
|
+
raise ArgumentError, "worker threads must be > 0" unless threads.is_a?(Integer) && threads.positive?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
119
134
|
def connection_options
|
|
120
135
|
if database_url
|
|
121
136
|
database_url
|
|
@@ -17,11 +17,7 @@ module Pgbus
|
|
|
17
17
|
raw = JSON.parse(message.message)
|
|
18
18
|
event = build_event(raw)
|
|
19
19
|
|
|
20
|
-
if self.class.idempotent?
|
|
21
|
-
return :skipped if already_processed?(event.event_id)
|
|
22
|
-
|
|
23
|
-
mark_processed!(event.event_id)
|
|
24
|
-
end
|
|
20
|
+
return :skipped if self.class.idempotent? && !claim_idempotency?(event.event_id)
|
|
25
21
|
|
|
26
22
|
handle(event)
|
|
27
23
|
instrument("pgbus.event_processed", event_id: event.event_id, handler: self.class.name)
|
|
@@ -51,15 +47,16 @@ module Pgbus
|
|
|
51
47
|
ActiveSupport::Notifications.instrument(event_name, payload)
|
|
52
48
|
end
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
ProcessedEvent.upsert(
|
|
50
|
+
# Atomically claim idempotency: INSERT ... ON CONFLICT DO NOTHING.
|
|
51
|
+
# Returns true if this handler claimed the event (row was inserted),
|
|
52
|
+
# false if another handler already processed it (conflict, no insert).
|
|
53
|
+
def claim_idempotency?(event_id)
|
|
54
|
+
result = ProcessedEvent.insert(
|
|
60
55
|
{ event_id: event_id, handler_class: self.class.name, processed_at: Time.now.utc },
|
|
61
56
|
unique_by: %i[event_id handler_class]
|
|
62
57
|
)
|
|
58
|
+
# insert returns an InsertAll::Result; inserted row count > 0 means we claimed it
|
|
59
|
+
result.rows.any?
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
end
|
|
@@ -71,6 +71,12 @@ module Pgbus
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def handle_message(message, queue_name)
|
|
74
|
+
if message.read_ct.to_i > config.max_retries
|
|
75
|
+
Pgbus.logger.warn { "[Pgbus] Consumer moving message #{message.msg_id} to DLQ after #{message.read_ct} reads" }
|
|
76
|
+
Pgbus.client.move_to_dead_letter(queue_name, message)
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
raw = JSON.parse(message.message)
|
|
75
81
|
routing_key = raw.dig("headers", "routing_key") || raw["routing_key"]
|
|
76
82
|
|
|
@@ -83,6 +89,9 @@ module Pgbus
|
|
|
83
89
|
Pgbus.client.archive_message(queue_name, message.msg_id.to_i)
|
|
84
90
|
rescue StandardError => e
|
|
85
91
|
Pgbus.logger.error { "[Pgbus] Consumer error: #{e.class}: #{e.message}" }
|
|
92
|
+
# Message stays in queue; VT will expire and it becomes available again.
|
|
93
|
+
# read_ct tracks delivery attempts — when it exceeds max_retries,
|
|
94
|
+
# the next read will route to DLQ above.
|
|
86
95
|
end
|
|
87
96
|
|
|
88
97
|
def pattern_overlaps?(topic_filter, subscription_pattern)
|
|
@@ -59,30 +59,20 @@ module Pgbus
|
|
|
59
59
|
def run_maintenance
|
|
60
60
|
now = Time.now
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
reap_stale_processes
|
|
69
|
-
@last_reap_at = now
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
if now - @last_concurrency_at >= CONCURRENCY_INTERVAL
|
|
73
|
-
cleanup_concurrency
|
|
74
|
-
@last_concurrency_at = now
|
|
75
|
-
end
|
|
62
|
+
run_if_due(now, :@last_cleanup_at, CLEANUP_INTERVAL) { cleanup_processed_events }
|
|
63
|
+
run_if_due(now, :@last_reap_at, REAP_INTERVAL) { reap_stale_processes }
|
|
64
|
+
run_if_due(now, :@last_concurrency_at, CONCURRENCY_INTERVAL) { cleanup_concurrency }
|
|
65
|
+
run_if_due(now, :@last_batch_cleanup_at, BATCH_CLEANUP_INTERVAL) { cleanup_batches }
|
|
66
|
+
run_if_due(now, :@last_recurring_cleanup_at, RECURRING_CLEANUP_INTERVAL) { cleanup_recurring_executions }
|
|
67
|
+
end
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
# Only update the timestamp when the block succeeds.
|
|
70
|
+
# On failure, the next tick retries instead of waiting the full interval.
|
|
71
|
+
def run_if_due(now, ivar, interval)
|
|
72
|
+
return unless now - instance_variable_get(ivar) >= interval
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@last_recurring_cleanup_at = now
|
|
85
|
-
end
|
|
74
|
+
yield
|
|
75
|
+
instance_variable_set(ivar, now)
|
|
86
76
|
rescue StandardError => e
|
|
87
77
|
Pgbus.logger.error { "[Pgbus] Dispatcher maintenance error: #{e.message}" }
|
|
88
78
|
end
|
|
@@ -69,8 +69,15 @@ module Pgbus
|
|
|
69
69
|
worker.run
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
unless pid
|
|
73
|
+
Pgbus.logger.error { "[Pgbus] Failed to fork worker for queues=#{queues.join(",")}" }
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
72
77
|
@forks[pid] = { type: :worker, config: worker_config }
|
|
73
78
|
Pgbus.logger.info { "[Pgbus] Forked worker pid=#{pid} queues=#{queues.join(",")}" }
|
|
79
|
+
rescue Errno::EAGAIN, Errno::ENOMEM => e
|
|
80
|
+
Pgbus.logger.error { "[Pgbus] Fork failed for worker: #{e.message}" }
|
|
74
81
|
end
|
|
75
82
|
|
|
76
83
|
def fork_dispatcher
|
|
@@ -82,8 +89,15 @@ module Pgbus
|
|
|
82
89
|
dispatcher.run
|
|
83
90
|
end
|
|
84
91
|
|
|
92
|
+
unless pid
|
|
93
|
+
Pgbus.logger.error { "[Pgbus] Failed to fork dispatcher" }
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
85
97
|
@forks[pid] = { type: :dispatcher }
|
|
86
98
|
Pgbus.logger.info { "[Pgbus] Forked dispatcher pid=#{pid}" }
|
|
99
|
+
rescue Errno::EAGAIN, Errno::ENOMEM => e
|
|
100
|
+
Pgbus.logger.error { "[Pgbus] Fork failed for dispatcher: #{e.message}" }
|
|
87
101
|
end
|
|
88
102
|
|
|
89
103
|
def boot_scheduler
|
|
@@ -103,8 +117,15 @@ module Pgbus
|
|
|
103
117
|
scheduler.run
|
|
104
118
|
end
|
|
105
119
|
|
|
120
|
+
unless pid
|
|
121
|
+
Pgbus.logger.error { "[Pgbus] Failed to fork scheduler" }
|
|
122
|
+
return
|
|
123
|
+
end
|
|
124
|
+
|
|
106
125
|
@forks[pid] = { type: :scheduler }
|
|
107
126
|
Pgbus.logger.info { "[Pgbus] Forked scheduler pid=#{pid}" }
|
|
127
|
+
rescue Errno::EAGAIN, Errno::ENOMEM => e
|
|
128
|
+
Pgbus.logger.error { "[Pgbus] Fork failed for scheduler: #{e.message}" }
|
|
108
129
|
end
|
|
109
130
|
|
|
110
131
|
def recurring_tasks_configured?
|
|
@@ -150,8 +171,15 @@ module Pgbus
|
|
|
150
171
|
consumer.run
|
|
151
172
|
end
|
|
152
173
|
|
|
174
|
+
unless pid
|
|
175
|
+
Pgbus.logger.error { "[Pgbus] Failed to fork consumer for topics=#{topics.join(",")}" }
|
|
176
|
+
return
|
|
177
|
+
end
|
|
178
|
+
|
|
153
179
|
@forks[pid] = { type: :consumer, config: consumer_config }
|
|
154
180
|
Pgbus.logger.info { "[Pgbus] Forked consumer pid=#{pid} topics=#{topics.join(",")}" }
|
|
181
|
+
rescue Errno::EAGAIN, Errno::ENOMEM => e
|
|
182
|
+
Pgbus.logger.error { "[Pgbus] Fork failed for consumer: #{e.message}" }
|
|
155
183
|
end
|
|
156
184
|
|
|
157
185
|
def monitor_loop
|
data/lib/pgbus/process/worker.rb
CHANGED
|
@@ -32,11 +32,12 @@ module Pgbus
|
|
|
32
32
|
Pgbus.logger.info { "[Pgbus] Worker started: queues=#{queues.join(",")} threads=#{threads} pid=#{::Process.pid}" }
|
|
33
33
|
|
|
34
34
|
loop do
|
|
35
|
-
break if @shutting_down
|
|
36
|
-
break if recycle_needed?
|
|
37
|
-
|
|
38
35
|
process_signals
|
|
39
|
-
|
|
36
|
+
break if @shutting_down && @pool.queue_length.zero?
|
|
37
|
+
break if recycle_needed? && @pool.queue_length.zero?
|
|
38
|
+
|
|
39
|
+
claim_and_execute unless @shutting_down || recycle_needed?
|
|
40
|
+
interruptible_sleep(config.polling_interval) if (@shutting_down || recycle_needed?) && !@pool.queue_length.zero?
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
shutdown
|
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
module Pgbus
|
|
4
4
|
module Recurring
|
|
5
5
|
# Job class for command-based recurring tasks.
|
|
6
|
-
#
|
|
6
|
+
# Evaluates a method call chain on a constant, e.g. "OldRecord.cleanup!".
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# Only supports `ConstantName.method_name` and `ConstantName.method_name(args)`.
|
|
9
|
+
# Rejects arbitrary Ruby expressions for safety.
|
|
10
10
|
class CommandJob < ::ActiveJob::Base
|
|
11
|
+
SAFE_COMMAND = /\A([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\.([a-z_][a-z0-9_!?]*)\z/
|
|
12
|
+
|
|
11
13
|
def perform(command)
|
|
12
|
-
|
|
14
|
+
match = SAFE_COMMAND.match(command)
|
|
15
|
+
unless match
|
|
16
|
+
raise ArgumentError,
|
|
17
|
+
"Unsafe recurring command: #{command.inspect}. " \
|
|
18
|
+
"Must be in the form 'ClassName.method_name'."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
klass = match[1].safe_constantize
|
|
22
|
+
raise ArgumentError, "Unknown class in recurring command: #{match[1]}" unless klass
|
|
23
|
+
|
|
24
|
+
klass.public_send(match[2])
|
|
13
25
|
end
|
|
14
26
|
end
|
|
15
27
|
end
|
data/lib/pgbus/serializer.rb
CHANGED
|
@@ -15,6 +15,12 @@ module Pgbus
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def serialize_job_hash(active_job)
|
|
19
|
+
Instrumentation.instrument("pgbus.serializer.serialize", kind: :job) do
|
|
20
|
+
active_job.serialize
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
def deserialize_job(json_string)
|
|
19
25
|
Instrumentation.instrument("pgbus.serializer.deserialize", kind: :job) do
|
|
20
26
|
data = JSON.parse(json_string)
|
data/lib/pgbus/version.rb
CHANGED
|
@@ -129,7 +129,9 @@ module Pgbus
|
|
|
129
129
|
|
|
130
130
|
connection.transaction do
|
|
131
131
|
@client.send_message(event["queue_name"], payload, headers: headers)
|
|
132
|
-
connection.
|
|
132
|
+
connection.exec_delete(
|
|
133
|
+
"DELETE FROM pgbus_failed_events WHERE id = $1", "Pgbus Delete Failed Event", [id.to_i]
|
|
134
|
+
)
|
|
133
135
|
end
|
|
134
136
|
true
|
|
135
137
|
rescue StandardError => e
|
|
@@ -138,7 +140,9 @@ module Pgbus
|
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
def discard_failed_event(id)
|
|
141
|
-
connection.
|
|
143
|
+
connection.exec_delete(
|
|
144
|
+
"DELETE FROM pgbus_failed_events WHERE id = $1", "Pgbus Delete Failed Event", [id.to_i]
|
|
145
|
+
)
|
|
142
146
|
true
|
|
143
147
|
rescue StandardError => e
|
|
144
148
|
Pgbus.logger.debug { "[Pgbus::Web] Error discarding failed event #{id}: #{e.message}" }
|
|
@@ -147,18 +151,27 @@ module Pgbus
|
|
|
147
151
|
|
|
148
152
|
def retry_all_failed
|
|
149
153
|
count = 0
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
loop do
|
|
155
|
+
batch = connection.select_all(
|
|
156
|
+
"SELECT * FROM pgbus_failed_events ORDER BY id LIMIT 100", "Pgbus Retry Batch"
|
|
157
|
+
).to_a
|
|
158
|
+
break if batch.empty?
|
|
159
|
+
|
|
160
|
+
batch.each do |event|
|
|
161
|
+
payload = JSON.parse(event["payload"])
|
|
162
|
+
headers = event["headers"]
|
|
163
|
+
headers = JSON.parse(headers) if headers.is_a?(String)
|
|
164
|
+
|
|
165
|
+
connection.transaction do
|
|
166
|
+
@client.send_message(event["queue_name"], payload, headers: headers)
|
|
167
|
+
connection.exec_delete(
|
|
168
|
+
"DELETE FROM pgbus_failed_events WHERE id = $1", "Pgbus Delete Failed Event", [event["id"].to_i]
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
count += 1
|
|
172
|
+
rescue StandardError => e
|
|
173
|
+
Pgbus.logger.error { "[Pgbus::Web] Failed to retry event #{event["id"]}: #{e.message}" }
|
|
158
174
|
end
|
|
159
|
-
count += 1
|
|
160
|
-
rescue StandardError => e
|
|
161
|
-
Pgbus.logger.error { "[Pgbus::Web] Failed to retry event #{event["id"]}: #{e.message}" }
|
|
162
175
|
end
|
|
163
176
|
count
|
|
164
177
|
end
|
|
@@ -552,7 +565,10 @@ module Pgbus
|
|
|
552
565
|
end
|
|
553
566
|
|
|
554
567
|
def sanitize_name(name)
|
|
555
|
-
name.gsub(/[^a-zA-Z0-9_]/, "")
|
|
568
|
+
sanitized = name.gsub(/[^a-zA-Z0-9_]/, "")
|
|
569
|
+
raise ArgumentError, "Invalid queue name: #{name.inspect}" if sanitized.empty?
|
|
570
|
+
|
|
571
|
+
sanitized
|
|
556
572
|
end
|
|
557
573
|
|
|
558
574
|
def parse_arguments(args)
|