pgbus 0.16.4 → 0.16.6
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 +9 -0
- data/app/controllers/pgbus/locks_controller.rb +27 -0
- data/app/frontend/pgbus/style.css +1 -1
- data/app/models/pgbus/uniqueness_key.rb +20 -0
- data/app/views/pgbus/dashboard/_stats_cards.html.erb +14 -1
- data/app/views/pgbus/locks/_concurrency.html.erb +94 -0
- data/app/views/pgbus/locks/_uniqueness.html.erb +80 -0
- data/app/views/pgbus/locks/index.html.erb +5 -76
- data/config/locales/da.yml +42 -1
- data/config/locales/de.yml +42 -1
- data/config/locales/en.yml +42 -1
- data/config/locales/es.yml +42 -1
- data/config/locales/fi.yml +42 -1
- data/config/locales/fr.yml +42 -1
- data/config/locales/it.yml +42 -1
- data/config/locales/ja.yml +42 -1
- data/config/locales/nb.yml +42 -1
- data/config/locales/nl.yml +42 -1
- data/config/locales/pt.yml +42 -1
- data/config/locales/sv.yml +42 -1
- data/config/routes.rb +5 -0
- data/lib/pgbus/event_bus/handler.rb +17 -3
- data/lib/pgbus/event_bus/stale_connection_retry.rb +85 -0
- data/lib/pgbus/instrumentation.rb +3 -0
- data/lib/pgbus/integrations/appsignal/dashboard.json +38 -0
- data/lib/pgbus/integrations/appsignal/probe.rb +10 -0
- data/lib/pgbus/mcp/base_tool.rb +6 -1
- data/lib/pgbus/mcp/server.rb +2 -1
- data/lib/pgbus/mcp/tools/concurrency_tool.rb +32 -0
- data/lib/pgbus/mcp.rb +1 -0
- data/lib/pgbus/streams/broadcast_opts.rb +70 -0
- data/lib/pgbus/streams/broadcastable_override.rb +7 -20
- data/lib/pgbus/streams/turbo_broadcastable.rb +65 -4
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +210 -1
- data/lib/pgbus/web/metrics_serializer.rb +51 -3
- metadata +6 -1
|
@@ -5,6 +5,11 @@ require "time"
|
|
|
5
5
|
module Pgbus
|
|
6
6
|
module Web
|
|
7
7
|
class DataSource
|
|
8
|
+
# Ceiling on how many parked jobs one dashboard release promotes, so a
|
|
9
|
+
# key with thousands parked cannot hold the request open. The dispatcher
|
|
10
|
+
# sweep picks up whatever is left on its next pass.
|
|
11
|
+
PROMOTE_CAP = 100
|
|
12
|
+
|
|
8
13
|
def initialize(client: Pgbus.client)
|
|
9
14
|
@client = client
|
|
10
15
|
@last_throughput_snapshot = nil
|
|
@@ -35,7 +40,18 @@ module Pgbus
|
|
|
35
40
|
total_dead_tuples: health[:total_dead_tuples],
|
|
36
41
|
tables_needing_vacuum: health[:tables_needing_vacuum],
|
|
37
42
|
oldest_transaction_age_sec: health[:oldest_transaction_age_sec]
|
|
38
|
-
}
|
|
43
|
+
}.merge(concurrency_summary)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Drop the per-instance memos. The memos assume one instance per web
|
|
47
|
+
# request; the AppSignal probe and the MCP server both hold ONE DataSource
|
|
48
|
+
# for the life of the process, so without this their gauges and tool
|
|
49
|
+
# responses would freeze at the first read. Both call this per iteration
|
|
50
|
+
# / per tool call.
|
|
51
|
+
def reset_cache!
|
|
52
|
+
@queues_with_metrics = nil
|
|
53
|
+
@concurrency_summary = nil
|
|
54
|
+
self
|
|
39
55
|
end
|
|
40
56
|
|
|
41
57
|
# Queues — query via ActiveRecord for reliability in web processes
|
|
@@ -667,6 +683,83 @@ module Pgbus
|
|
|
667
683
|
[]
|
|
668
684
|
end
|
|
669
685
|
|
|
686
|
+
# Concurrency keys — the `limits_concurrency` slot table and the jobs
|
|
687
|
+
# parked behind it. Aggregate numbers plus up to 100 key rows, busiest
|
|
688
|
+
# first. The two tables are joined FULL OUTER, not LEFT: a key can have
|
|
689
|
+
# parked rows and no semaphore (its holder died and the sweep removed the
|
|
690
|
+
# row) or a semaphore and nothing parked — an operator needs to see both.
|
|
691
|
+
def concurrency_stats
|
|
692
|
+
concurrency_summary.merge(keys: concurrency_keys)
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# Drop a key's semaphore row and promote whatever can now run.
|
|
696
|
+
#
|
|
697
|
+
# Promotion goes through Concurrency::BlockedExecution.promote_next, which
|
|
698
|
+
# takes each slot through the same guarded upsert an enqueue uses — a
|
|
699
|
+
# dashboard release can no more over-admit than an enqueue can. Re-sending
|
|
700
|
+
# the parked payloads directly would reintroduce exactly the over-admission
|
|
701
|
+
# issue #460 closed. Returns the number of jobs promoted.
|
|
702
|
+
def release_concurrency_key(key)
|
|
703
|
+
return 0 if key.to_s.strip.empty?
|
|
704
|
+
|
|
705
|
+
# Zero the held slots rather than DELETE the row: the row is where the
|
|
706
|
+
# key's limit is recorded, and `Semaphore.acquire!` falls back to a
|
|
707
|
+
# limit of 1 on a fresh row. Deleting it would silently demote a
|
|
708
|
+
# `to: 3` key to 1 whenever the parked job's class no longer resolves
|
|
709
|
+
# (the payload then carries no limit for promotion to use). A row left
|
|
710
|
+
# at 0 is reaped by the dispatcher's `Semaphore.expired` sweep.
|
|
711
|
+
Pgbus::Semaphore.where(key: key).update_all(value: 0)
|
|
712
|
+
promoted = 0
|
|
713
|
+
promoted += 1 while promoted < PROMOTE_CAP &&
|
|
714
|
+
Concurrency::BlockedExecution.promote_next(key, client: @client)
|
|
715
|
+
|
|
716
|
+
# Nothing took the freed slot, so mark the empty row expired: the
|
|
717
|
+
# dispatcher's `Concurrency::Semaphore.expire_stale` matches on
|
|
718
|
+
# expires_at ALONE, so a zeroed row would otherwise sit on the Locks
|
|
719
|
+
# page as a phantom 0/N key until its original lease ran out — up to
|
|
720
|
+
# the key's whole `duration`. Guarded on value = 0 so a row a promotion
|
|
721
|
+
# just filled is never expired out from under its holder.
|
|
722
|
+
Pgbus::Semaphore.where(key: key, value: 0).update_all(expires_at: Time.current)
|
|
723
|
+
promoted
|
|
724
|
+
rescue StandardError => e
|
|
725
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error releasing concurrency key #{key}: #{e.message}" }
|
|
726
|
+
0
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Drop every job parked behind a key. The jobs never run, so the
|
|
730
|
+
# bookkeeping they would have resolved has to be resolved here: a parked
|
|
731
|
+
# batch child is marked failed (otherwise its batch waits forever and
|
|
732
|
+
# on_failure never fires — issue #413) and an :until_executed uniqueness
|
|
733
|
+
# lock is released (otherwise it is orphaned, since no executor will ever
|
|
734
|
+
# release it — issue #423). Returns the number of jobs discarded.
|
|
735
|
+
def discard_parked_jobs(key)
|
|
736
|
+
return 0 if key.to_s.strip.empty?
|
|
737
|
+
|
|
738
|
+
rows = []
|
|
739
|
+
Pgbus::BlockedExecution.transaction do
|
|
740
|
+
# SKIP LOCKED so this can never race a concurrent promote, which
|
|
741
|
+
# deletes its row under the same lock before enqueueing it.
|
|
742
|
+
rows = Pgbus::BlockedExecution.for_key(key).lock("FOR UPDATE SKIP LOCKED").to_a
|
|
743
|
+
Pgbus::BlockedExecution.where(id: rows.map(&:id)).delete_all if rows.any?
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# Cleanup runs after the claim commits, because the SKIP LOCKED claim
|
|
747
|
+
# only holds inside the transaction. The residual: a process killed
|
|
748
|
+
# between the commit and the cleanup leaves a resolved-nothing orphan
|
|
749
|
+
# that Batch::Sweep un-counts rather than fails. Running the cleanup
|
|
750
|
+
# inside the transaction would swap that for the failure this codebase
|
|
751
|
+
# has already judged worse (Concurrency::BlockedExecution#backfill) —
|
|
752
|
+
# `Batch.job_discarded` can enqueue a batch callback, and a callback
|
|
753
|
+
# fired for a batch that then rolls back is not recoverable, while an
|
|
754
|
+
# orphan row is. Each row is cleaned under its own rescue, so one bad
|
|
755
|
+
# payload never costs the rest.
|
|
756
|
+
rows.each { |row| cleanup_discarded_parked_job(row) }
|
|
757
|
+
rows.size
|
|
758
|
+
rescue StandardError => e
|
|
759
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error discarding parked jobs for #{key}: #{e.message}" }
|
|
760
|
+
0
|
|
761
|
+
end
|
|
762
|
+
|
|
670
763
|
# Batches
|
|
671
764
|
def batches(limit: 100)
|
|
672
765
|
records = BatchEntry.order(created_at: :desc).limit(limit).to_a
|
|
@@ -1026,6 +1119,122 @@ module Pgbus
|
|
|
1026
1119
|
Pgbus::BusRecord.connection
|
|
1027
1120
|
end
|
|
1028
1121
|
|
|
1122
|
+
# Aggregate concurrency numbers, memoized for the lifetime of this
|
|
1123
|
+
# instance: summary_stats, the metrics serializer and the AppSignal probe
|
|
1124
|
+
# all read them on the same request, and concurrency_stats merges them in
|
|
1125
|
+
# alongside the key rows. Mutations redirect to a fresh request with a new
|
|
1126
|
+
# instance, so the memo never serves stale numbers.
|
|
1127
|
+
def concurrency_summary
|
|
1128
|
+
@concurrency_summary ||= fetch_concurrency_summary
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def fetch_concurrency_summary
|
|
1132
|
+
row = connection.select_all(<<~SQL, "Pgbus Concurrency Summary").to_a.first || {}
|
|
1133
|
+
SELECT
|
|
1134
|
+
(SELECT COUNT(*) FROM pgbus_blocked_executions) AS parked_total,
|
|
1135
|
+
(SELECT EXTRACT(EPOCH FROM (now() - MIN(created_at)))::bigint
|
|
1136
|
+
FROM pgbus_blocked_executions) AS oldest_parked_age_sec,
|
|
1137
|
+
(SELECT COALESCE(SUM(value), 0) FROM pgbus_semaphores) AS slots_held,
|
|
1138
|
+
(SELECT COUNT(*) FROM pgbus_semaphores WHERE value >= max_value) AS keys_at_limit
|
|
1139
|
+
SQL
|
|
1140
|
+
|
|
1141
|
+
{
|
|
1142
|
+
parked_total: row["parked_total"].to_i,
|
|
1143
|
+
oldest_parked_age_sec: row["oldest_parked_age_sec"]&.to_i,
|
|
1144
|
+
slots_held: row["slots_held"].to_i,
|
|
1145
|
+
keys_at_limit: row["keys_at_limit"].to_i
|
|
1146
|
+
}
|
|
1147
|
+
rescue StandardError => e
|
|
1148
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error fetching concurrency summary: #{e.message}" }
|
|
1149
|
+
{ parked_total: 0, oldest_parked_age_sec: nil, slots_held: 0, keys_at_limit: 0 }
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
# `lease_fresh` is the one fact an operator needs before releasing a key:
|
|
1153
|
+
# a live lease means a holder is probably still running, so releasing lets
|
|
1154
|
+
# another job start beside it.
|
|
1155
|
+
def concurrency_keys(limit: 100)
|
|
1156
|
+
rows = connection.select_all(<<~SQL, "Pgbus Concurrency Keys")
|
|
1157
|
+
SELECT COALESCE(s.key, b.concurrency_key) AS key,
|
|
1158
|
+
s.value AS value,
|
|
1159
|
+
s.max_value AS max_value,
|
|
1160
|
+
s.expires_at AS expires_at,
|
|
1161
|
+
(s.value > 0 AND s.expires_at > now()) AS lease_fresh,
|
|
1162
|
+
COALESCE(b.parked_count, 0) AS parked_count,
|
|
1163
|
+
EXTRACT(EPOCH FROM (now() - b.oldest_parked_at))::bigint AS oldest_parked_age_sec
|
|
1164
|
+
FROM pgbus_semaphores s
|
|
1165
|
+
FULL OUTER JOIN (
|
|
1166
|
+
SELECT concurrency_key, COUNT(*) AS parked_count, MIN(created_at) AS oldest_parked_at
|
|
1167
|
+
FROM pgbus_blocked_executions
|
|
1168
|
+
GROUP BY concurrency_key
|
|
1169
|
+
) b ON s.key = b.concurrency_key
|
|
1170
|
+
ORDER BY COALESCE(b.parked_count, 0) DESC, s.expires_at ASC NULLS LAST
|
|
1171
|
+
LIMIT #{limit.to_i}
|
|
1172
|
+
SQL
|
|
1173
|
+
|
|
1174
|
+
rows.to_a.map { |row| format_concurrency_key(row) }
|
|
1175
|
+
rescue StandardError => e
|
|
1176
|
+
Pgbus.logger.debug { "[Pgbus::Web] Error fetching concurrency keys: #{e.message}" }
|
|
1177
|
+
[]
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
def format_concurrency_key(row)
|
|
1181
|
+
{
|
|
1182
|
+
key: row["key"],
|
|
1183
|
+
value: row["value"]&.to_i,
|
|
1184
|
+
max_value: row["max_value"]&.to_i,
|
|
1185
|
+
expires_at: row["expires_at"],
|
|
1186
|
+
lease_fresh: [true, "t"].include?(row["lease_fresh"]),
|
|
1187
|
+
parked_count: row["parked_count"].to_i,
|
|
1188
|
+
oldest_parked_age_sec: row["oldest_parked_age_sec"]&.to_i
|
|
1189
|
+
}
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
# Resolve the bookkeeping a discarded parked job will never resolve
|
|
1193
|
+
# itself. Per-row rather than batched: one bad payload must not stop the
|
|
1194
|
+
# rest from being cleaned up, and the rows are already deleted.
|
|
1195
|
+
def cleanup_discarded_parked_job(row)
|
|
1196
|
+
payload = decode_parked_payload(row.payload)
|
|
1197
|
+
batch_id = payload[Batch::METADATA_KEY]
|
|
1198
|
+
Batch.job_discarded(batch_id, job_id: payload["job_id"]) if batch_id
|
|
1199
|
+
|
|
1200
|
+
release_parked_uniqueness_lock(payload, row.created_at)
|
|
1201
|
+
|
|
1202
|
+
Pgbus.logger.warn do
|
|
1203
|
+
"[Pgbus::Web] Discarded parked job #{payload["job_class"]} (#{payload["job_id"]}) " \
|
|
1204
|
+
"for concurrency key #{row.concurrency_key}"
|
|
1205
|
+
end
|
|
1206
|
+
Instrumentation.instrument(
|
|
1207
|
+
"pgbus.blocked_execution_discarded",
|
|
1208
|
+
concurrency_key: row.concurrency_key,
|
|
1209
|
+
job_class: payload["job_class"],
|
|
1210
|
+
job_id: payload["job_id"]
|
|
1211
|
+
)
|
|
1212
|
+
rescue StandardError => e
|
|
1213
|
+
Pgbus.logger.warn { "[Pgbus::Web] Parked job cleanup failed: #{e.class}: #{e.message}" }
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# release_if_unbound!, not release_lock: a parked job's lock is unbound (it
|
|
1217
|
+
# never got a msg_id). If the reaper already removed it and a successor
|
|
1218
|
+
# took the same key, that successor's lock must survive — msg_id = 0
|
|
1219
|
+
# excludes one that was sent, and the parked row's own created_at excludes
|
|
1220
|
+
# one acquired after this row was parked.
|
|
1221
|
+
def release_parked_uniqueness_lock(payload, parked_at)
|
|
1222
|
+
key = payload[Uniqueness::METADATA_KEY]
|
|
1223
|
+
return unless key && payload[Uniqueness::STRATEGY_KEY].to_s == "until_executed"
|
|
1224
|
+
|
|
1225
|
+
UniquenessKey.release_if_unbound!(key, acquired_before: parked_at)
|
|
1226
|
+
end
|
|
1227
|
+
|
|
1228
|
+
# A row parked before the double-encoding fix stores a jsonb *string*
|
|
1229
|
+
# holding the document — the same two-pass unwrap BlockedExecution
|
|
1230
|
+
# .release_next! does.
|
|
1231
|
+
def decode_parked_payload(payload)
|
|
1232
|
+
2.times { payload = JSON.parse(payload) if payload.is_a?(String) }
|
|
1233
|
+
payload.is_a?(Hash) ? payload : {}
|
|
1234
|
+
rescue JSON::ParserError
|
|
1235
|
+
{}
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1029
1238
|
# Single query to fetch pg_stat_user_tables stats for all queue and
|
|
1030
1239
|
# archive tables. Avoids 2*N catalog queries on the dashboard.
|
|
1031
1240
|
def fetch_all_table_stats
|
|
@@ -22,9 +22,14 @@ module Pgbus
|
|
|
22
22
|
append_queue_metrics(lines)
|
|
23
23
|
append_job_metrics(lines)
|
|
24
24
|
append_process_metrics(lines)
|
|
25
|
-
|
|
25
|
+
# One summary read, shared: summary_stats is not memoized, so calling it
|
|
26
|
+
# per family would repeat the queue/health/process queries and advance
|
|
27
|
+
# the throughput snapshot a second time within one scrape.
|
|
28
|
+
summary = summary_stats
|
|
29
|
+
append_summary_metrics(lines, summary)
|
|
26
30
|
append_stream_metrics(lines)
|
|
27
31
|
append_health_metrics(lines)
|
|
32
|
+
append_concurrency_metrics(lines, summary)
|
|
28
33
|
"#{lines.join("\n")}\n"
|
|
29
34
|
end
|
|
30
35
|
|
|
@@ -147,8 +152,18 @@ module Pgbus
|
|
|
147
152
|
Pgbus.logger.debug { "[Pgbus::Metrics] Error serializing process metrics: #{e.message}" }
|
|
148
153
|
end
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
# nil when the read raised — each family then skips rather than blanking
|
|
156
|
+
# the whole scrape.
|
|
157
|
+
def summary_stats
|
|
158
|
+
@data_source.summary_stats
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
Pgbus.logger.debug { "[Pgbus::Metrics] Error reading summary stats: #{e.message}" }
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def append_summary_metrics(lines, stats)
|
|
165
|
+
return unless stats
|
|
166
|
+
|
|
152
167
|
gauge(lines, "pgbus_failed_events_total", "Total failed events") do
|
|
153
168
|
[[stats[:failed_count]]]
|
|
154
169
|
end
|
|
@@ -219,6 +234,39 @@ module Pgbus
|
|
|
219
234
|
Pgbus.logger.debug { "[Pgbus::Metrics] Error serializing health metrics: #{e.message}" }
|
|
220
235
|
end
|
|
221
236
|
|
|
237
|
+
# Concurrency slot pressure: how many jobs are parked, how long the oldest
|
|
238
|
+
# has waited, how many slots are held right now. Unlabelled on purpose —
|
|
239
|
+
# concurrency keys are per-record (one per order, one per sync group), so a
|
|
240
|
+
# per-key label would make an unbounded series. The per-key detail lives on
|
|
241
|
+
# the Locks page and in the pgbus_concurrency MCP tool, both bounded.
|
|
242
|
+
#
|
|
243
|
+
# Omitted entirely when neither table holds anything: an install that does
|
|
244
|
+
# not use limits_concurrency reports nothing rather than a flat zero line.
|
|
245
|
+
def append_concurrency_metrics(lines, stats)
|
|
246
|
+
return unless stats
|
|
247
|
+
return if stats[:parked_total].to_i.zero? && stats[:slots_held].to_i.zero? &&
|
|
248
|
+
stats[:keys_at_limit].to_i.zero? && stats[:oldest_parked_age_sec].nil?
|
|
249
|
+
|
|
250
|
+
gauge(lines, "pgbus_concurrency_blocked_executions",
|
|
251
|
+
"Jobs parked behind a concurrency key, waiting for a slot") do
|
|
252
|
+
[[stats[:parked_total].to_i]]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if stats[:oldest_parked_age_sec]
|
|
256
|
+
gauge(lines, "pgbus_concurrency_blocked_oldest_age_seconds",
|
|
257
|
+
"How long the longest-waiting parked job has waited") do
|
|
258
|
+
[[stats[:oldest_parked_age_sec]]]
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
gauge(lines, "pgbus_concurrency_slots_held",
|
|
263
|
+
"Concurrency slots currently held across all keys") do
|
|
264
|
+
[[stats[:slots_held].to_i]]
|
|
265
|
+
end
|
|
266
|
+
rescue StandardError => e
|
|
267
|
+
Pgbus.logger.debug { "[Pgbus::Metrics] Error serializing concurrency metrics: #{e.message}" }
|
|
268
|
+
end
|
|
269
|
+
|
|
222
270
|
# Emits a Prometheus gauge metric family. The block must return an array
|
|
223
271
|
# of [value] or [value, { label: "val" }] pairs.
|
|
224
272
|
def gauge(lines, name, help)
|
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.
|
|
4
|
+
version: 0.16.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -185,6 +185,8 @@ files:
|
|
|
185
185
|
- app/views/pgbus/jobs/_failed_table.html.erb
|
|
186
186
|
- app/views/pgbus/jobs/index.html.erb
|
|
187
187
|
- app/views/pgbus/jobs/show.html.erb
|
|
188
|
+
- app/views/pgbus/locks/_concurrency.html.erb
|
|
189
|
+
- app/views/pgbus/locks/_uniqueness.html.erb
|
|
188
190
|
- app/views/pgbus/locks/index.html.erb
|
|
189
191
|
- app/views/pgbus/outbox/index.html.erb
|
|
190
192
|
- app/views/pgbus/processes/_processes_table.html.erb
|
|
@@ -292,6 +294,7 @@ files:
|
|
|
292
294
|
- lib/pgbus/event_bus/handler.rb
|
|
293
295
|
- lib/pgbus/event_bus/publisher.rb
|
|
294
296
|
- lib/pgbus/event_bus/registry.rb
|
|
297
|
+
- lib/pgbus/event_bus/stale_connection_retry.rb
|
|
295
298
|
- lib/pgbus/event_bus/subscriber.rb
|
|
296
299
|
- lib/pgbus/execution_pools.rb
|
|
297
300
|
- lib/pgbus/execution_pools/async_pool.rb
|
|
@@ -317,6 +320,7 @@ files:
|
|
|
317
320
|
- lib/pgbus/mcp/redactor.rb
|
|
318
321
|
- lib/pgbus/mcp/runner.rb
|
|
319
322
|
- lib/pgbus/mcp/server.rb
|
|
323
|
+
- lib/pgbus/mcp/tools/concurrency_tool.rb
|
|
320
324
|
- lib/pgbus/mcp/tools/dlq_detail_tool.rb
|
|
321
325
|
- lib/pgbus/mcp/tools/dlq_tool.rb
|
|
322
326
|
- lib/pgbus/mcp/tools/health_tool.rb
|
|
@@ -372,6 +376,7 @@ files:
|
|
|
372
376
|
- lib/pgbus/serializer.rb
|
|
373
377
|
- lib/pgbus/stat_buffer.rb
|
|
374
378
|
- lib/pgbus/streams.rb
|
|
379
|
+
- lib/pgbus/streams/broadcast_opts.rb
|
|
375
380
|
- lib/pgbus/streams/broadcastable_override.rb
|
|
376
381
|
- lib/pgbus/streams/coalescer.rb
|
|
377
382
|
- lib/pgbus/streams/cursor.rb
|