pgbus 0.9.9 → 0.9.10
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 +3 -0
- data/lib/pgbus/mcp/rack_app.rb +18 -6
- data/lib/pgbus/process/dispatcher.rb +16 -4
- data/lib/pgbus/version.rb +1 -1
- 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: 26160848a1d65cad46065b62fb4b5df79a18756514a39f86b5c074d1a1fc4dad
|
|
4
|
+
data.tar.gz: f3c1551c80e1a2fce0188a20e732e3a84c57628abf18a3239569863cde546f56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 856d96cd1085bbc88c96ad6528417fdea7227918c883af4395c1bbf9b8610bb786326bff3793fbb29f0fea8615d16a5661b782dd38e9a02bdf55748c03074e0a
|
|
7
|
+
data.tar.gz: c7b2061b500ad2d5e7e5bba04e9de24ec397006112e0102d9778f2d9114d9802d1467ad285d211183965b8f1cfb551271a265b8402b577a313f19a5410e35db0
|
data/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
### Fixed
|
|
16
16
|
|
|
17
|
+
- **Durable stream `q_` queues no longer leak — the orphan sweep is now age-based, as its `streams_orphan_threshold` config always implied.** A durable stream's live `pgmq.q_<name>` table is never drained by normal delivery: the streamer peeks (`read_after` UNIONs `q_`+`a_` without claiming rows), so `queue_length` stays `> 0` for any durable stream ever broadcast to, and retention only prunes `a_` (which stays empty for streams). The orphan sweep dropped only *empty* queues, so it never collected a durable stream — a permanent leak of the queue table, archive table, indexes, and a `pgmq.meta` row, at worst one leaked queue *per request* for per-render/one-shot durable keys. `streams_orphan_threshold` (default 24h) was named and defaulted as an age but wired only as an on/off gate. `sweep_orphan_streams` now reads each queue's `pgmq.meta.created_at` (no schema change) and drops when the queue is empty **or** has aged past the threshold. Idempotent re-creation (`ensure_stream_queue`) plus the existing missing-queue tolerance in `read_after` keep it safe; a genuinely long-lived stream only loses replay history past the threshold, well clear of normal reconnect windows. A `nil` `created_at` is treated as young (never dropped on age). Refs #306.
|
|
18
|
+
- **`Pgbus::MCP.rack_app` now returns a clean 401 on auth failure instead of a 500.** The `RackApp` auth gate returned a *frozen* `UNAUTHORIZED` response triple; once mounted in Rails, response-mutating middleware downstream (`Rack::TempfileReaper` assigns `response[2]`, `Rack::ETag` adds a header) raised `FrozenError`, so every request with a missing/mismatched `PGBUS_MCP_TOKEN` surfaced as an opaque 500 in the host app's error tracker rather than the intended 401. The gate now builds a fresh, mutable array and headers hash per call (only the JSON body string stays memoized+frozen). A request-level spec drives the app through the real `TempfileReaper`/`ETag` stack to guard against regression. Refs #304.
|
|
19
|
+
|
|
17
20
|
### Security
|
|
18
21
|
|
|
19
22
|
## [0.9.8.1] - 2026-07-04
|
data/lib/pgbus/mcp/rack_app.rb
CHANGED
|
@@ -22,11 +22,12 @@ module Pgbus
|
|
|
22
22
|
# / behind your VPN, never internet-exposed.
|
|
23
23
|
class RackApp
|
|
24
24
|
BEARER_PREFIX = "Bearer "
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
# Only the JSON body string is frozen and reused. The outer response triple
|
|
26
|
+
# and its headers hash MUST be built fresh per call (#unauthorized) so
|
|
27
|
+
# downstream Rack middleware can mutate them — Rack::TempfileReaper assigns
|
|
28
|
+
# response[2] and Rack::ETag adds headers. Returning a frozen array/hash
|
|
29
|
+
# raised FrozenError → 500 instead of 401 (issue #304).
|
|
30
|
+
UNAUTHORIZED_BODY = { jsonrpc: "2.0", id: nil, error: { code: -32_001, message: "Unauthorized" } }.to_json.freeze
|
|
30
31
|
|
|
31
32
|
# @param data_source [Pgbus::Web::DataSource] read layer the tools query.
|
|
32
33
|
# Built once and shared: DataSource acquires Pgbus::BusRecord.connection
|
|
@@ -55,13 +56,24 @@ module Pgbus
|
|
|
55
56
|
# the gem's transport has no auth of its own.
|
|
56
57
|
def call(env)
|
|
57
58
|
request = Rack::Request.new(env)
|
|
58
|
-
return
|
|
59
|
+
return unauthorized unless authorized?(request)
|
|
59
60
|
|
|
60
61
|
@transport.handle_request(request)
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
private
|
|
64
65
|
|
|
66
|
+
# A fresh, mutable 401 response triple each call. See UNAUTHORIZED_BODY.
|
|
67
|
+
# Header keys are lowercase per the Rack 3 SPEC (matches stream_app.rb and
|
|
68
|
+
# prometheus_exporter.rb); Rack::Headers looks them up case-insensitively.
|
|
69
|
+
def unauthorized
|
|
70
|
+
[
|
|
71
|
+
401,
|
|
72
|
+
{ "content-type" => "application/json", "www-authenticate" => "Bearer" },
|
|
73
|
+
[UNAUTHORIZED_BODY]
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
|
|
65
77
|
# An explicit auth callable wins; otherwise fall back to the bearer token;
|
|
66
78
|
# otherwise (neither configured) allow — the constructor already warned.
|
|
67
79
|
def authorized?(request)
|
|
@@ -539,17 +539,29 @@ module Pgbus
|
|
|
539
539
|
break if interrupted?("Orphan stream sweep", index, queue_names.size)
|
|
540
540
|
next unless full_name.start_with?("#{prefix}_")
|
|
541
541
|
|
|
542
|
+
safe_name = QueueNameValidator.sanitize!(full_name)
|
|
542
543
|
row = conn.select_one(<<~SQL, "Pgbus Orphan Check")
|
|
543
|
-
SELECT
|
|
544
|
-
|
|
544
|
+
SELECT
|
|
545
|
+
(SELECT count(*) FROM pgmq.q_#{safe_name}) AS queue_length,
|
|
546
|
+
(SELECT EXTRACT(epoch FROM (NOW() - created_at))::int
|
|
547
|
+
FROM pgmq.meta WHERE queue_name = '#{safe_name}') AS age_sec
|
|
545
548
|
SQL
|
|
546
549
|
|
|
547
550
|
next unless row
|
|
548
|
-
|
|
551
|
+
|
|
552
|
+
empty = !row["queue_length"].to_i.positive?
|
|
553
|
+
# Durable stream q_ tables are never drained by delivery (non-consuming
|
|
554
|
+
# peek), so a non-empty queue that has aged past the threshold is a
|
|
555
|
+
# leak, not a live stream — drop it. A nil age_sec (unknown created_at)
|
|
556
|
+
# is treated as young, never as infinitely old.
|
|
557
|
+
age_sec = row["age_sec"]
|
|
558
|
+
stale = !age_sec.nil? && age_sec.to_i >= threshold.to_i
|
|
559
|
+
next unless empty || stale
|
|
549
560
|
|
|
550
561
|
Pgbus.client.drop_queue(full_name, prefixed: false)
|
|
551
562
|
dropped += 1
|
|
552
|
-
|
|
563
|
+
reason = empty ? "empty" : "aged past #{threshold.to_i}s threshold"
|
|
564
|
+
Pgbus.logger.info { "[Pgbus] Dropped orphan stream queue (#{reason}): #{full_name}" }
|
|
553
565
|
outcome.record_success
|
|
554
566
|
rescue StandardError => e
|
|
555
567
|
Pgbus.logger.warn { "[Pgbus] Orphan stream sweep failed for #{full_name}: #{e.message}" }
|
data/lib/pgbus/version.rb
CHANGED