pgbus 0.23.0 → 0.23.1
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 +6 -0
- data/README.md +5 -0
- data/lib/pgbus/client.rb +36 -0
- 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: 9d5cf17ed290df4dfd9ab65b43e594a67c2050ed0b4c9b6b3d19c886f9a7191c
|
|
4
|
+
data.tar.gz: 902f6b6c806ef2861d42fed64a9d0e1283c4165c2df44333a517d8c45f5c55cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 749638cf39ec81aeb595b5b8104dae598253d02278ee68b22da1f7edd949434c2691d5415b02acecc69c486ef4c487dbae241dd96b80d7ee8ffba723e1b9b029
|
|
7
|
+
data.tar.gz: c1c134bc2d1347cfe26348b1742a05fd44eba7cb567574690418c05ff53cb0af359f1695a099ef99f0ea05f56ab621f9a0b69e6bbb10a5c63dd098f3cc64354c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.23.1] - 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A stale-connection retry drops the pooled connections before its last attempt, so a connection libpq left mid-reset no longer fails every call until the process restarts (issue #645).** A `PG::Connection#reset` that `Thread#raise` interrupted while it was polling (a request-timeout middleware, a forced shutdown) leaves the pooled connection in `CONNECTION_STARTED`: neither `finished?` nor `CONNECTION_BAD`, so pgmq-ruby's checkout health check keeps handing it out and libpq refuses every send with `PQsendQueryParams no connection to the server`. `with_stale_connection_retry` matched that as stale and retried twice on the same socket, so every enqueue and read from that process failed (seen for 15+ minutes after a network partition, on one web worker, until it was restarted). The first retry is unchanged: it goes through pgmq-ruby's health check alone, which replaces a socket the server closed without touching the rest of the pool. When that retry fails too, the client drops the idle connections of both pools (the job pool and the streams pool, as `Client#reload` does) before the last one, and logs one warning, `Dropping pooled connections before the last stale-connection retry (attempt 2/2): <error>`. Connections other threads hold are discarded when they come back, as on the #354 wedged-read path. Dedicated connections only: on the shared ActiveRecord connection (a `connection_params` Proc) the pools are never reloaded, and nothing extra is logged. Each pool reloads on its own, so one reload that raises does not stop the other; a failure is logged at warn and the retry still runs; the caller still sees the stale `ConnectionError` if the retry fails. A non-stale `ConnectionError` (a pool timeout, a completion whose result was lost) is neither retried nor reloaded, as before. Refs #645.
|
|
8
|
+
|
|
3
9
|
## [0.23.0] - 2026-11-02
|
|
4
10
|
|
|
5
11
|
### Added
|
data/README.md
CHANGED
|
@@ -25,6 +25,7 @@ PostgreSQL-native job processing and event bus for Rails, built on [PGMQ](https:
|
|
|
25
25
|
- [Circuit breaker and queue pause/resume](#circuit-breaker-and-queue-pauseresume)
|
|
26
26
|
- [Client-level circuit breaker (database-down)](#client-level-circuit-breaker-database-down)
|
|
27
27
|
- [Read timeouts (libpq-native)](#read-timeouts-libpq-native)
|
|
28
|
+
- [Stale connection retries](#stale-connection-retries)
|
|
28
29
|
- [Prefetch flow control](#prefetch-flow-control)
|
|
29
30
|
- [Read-ahead](#read-ahead)
|
|
30
31
|
- [Worker recycling](#worker-recycling)
|
|
@@ -550,6 +551,10 @@ production:
|
|
|
550
551
|
|
|
551
552
|
A custom RuboCop cop, `Pgbus/NoRubyTimeout`, bans `Timeout.timeout` project-wide to prevent this class of bug from regressing.
|
|
552
553
|
|
|
554
|
+
### Stale connection retries
|
|
555
|
+
|
|
556
|
+
A `PGMQ::Errors::ConnectionError` whose message says the pooled connection was dead before it was used (`no connection to the server`, `connection is closed`, a PgBouncer-closed socket and the like) is retried twice, after about 0.1 s and 0.5 s. Errors that may have reached the server (`server closed the connection unexpectedly`) and pool timeouts are never retried. The first retry relies on pgmq-ruby's checkout health check, which replaces a closed connection. On a dedicated connection (`database_url` or `connection_params`), before the last retry, pgbus drops the idle connections of both pools (job and streams) and logs `Dropping pooled connections before the last stale-connection retry`. This recovers a connection the health check keeps handing out, such as one a `Thread#raise` left half-way through a reset (`CONNECTION_STARTED`). A read interrupted by the Ruby `Timeout` fallback above reloads the job pool the same way. `Pgbus.client.reload` drops the idle connections of both pools by hand. None of these reload anything on the shared ActiveRecord `Proc` connection, whose socket pgbus does not own.
|
|
557
|
+
|
|
553
558
|
### Prefetch flow control
|
|
554
559
|
|
|
555
560
|
Cap the number of in-flight (claimed but unfinished) messages per worker:
|
data/lib/pgbus/client.rb
CHANGED
|
@@ -1876,6 +1876,16 @@ module Pgbus
|
|
|
1876
1876
|
# restart or a brief failover — that outlasts the first immediate retry
|
|
1877
1877
|
# still gets a second, backed-off chance rather than failing an enqueue
|
|
1878
1878
|
# the caller may never retry.
|
|
1879
|
+
#
|
|
1880
|
+
# The last retry runs on freshly reloaded pools (issue #645). A reset that
|
|
1881
|
+
# Thread#raise interrupted mid-poll leaves the pooled PG::Connection in
|
|
1882
|
+
# CONNECTION_STARTED: neither finished? nor CONNECTION_BAD, so pgmq-ruby's
|
|
1883
|
+
# checkout health check keeps handing it out and libpq refuses every send
|
|
1884
|
+
# ("no connection to the server"). The first retry still goes through that
|
|
1885
|
+
# health check alone, which replaces a socket the server closed without
|
|
1886
|
+
# touching the rest of the pool; only when it fails too are both pools'
|
|
1887
|
+
# idle connections dropped (reload_pools_before_last_stale_retry), as the
|
|
1888
|
+
# #354 wedged-read path does.
|
|
1879
1889
|
STALE_RETRY_ATTEMPTS = 2
|
|
1880
1890
|
private_constant :STALE_RETRY_ATTEMPTS
|
|
1881
1891
|
|
|
@@ -2007,6 +2017,31 @@ module Pgbus
|
|
|
2007
2017
|
end
|
|
2008
2018
|
end
|
|
2009
2019
|
|
|
2020
|
+
# Drop both pools' idle connections before the last stale retry (issue
|
|
2021
|
+
# #645; see STALE_RETRY_ATTEMPTS), so it checks out a fresh connection
|
|
2022
|
+
# instead of the one pgmq-ruby's health check kept handing back. Skipped
|
|
2023
|
+
# silently on the shared-AR Proc path: those slots wrap ActiveRecord's own
|
|
2024
|
+
# socket (#reload refuses there with a warning that would be noise on every
|
|
2025
|
+
# retry). Each pool reloads on its own, so one failing does not keep the
|
|
2026
|
+
# other's connections. A reload failure is logged, never raised: the stale
|
|
2027
|
+
# ConnectionError the retry may raise next is the actionable error.
|
|
2028
|
+
def reload_pools_before_last_stale_retry(attempt, error)
|
|
2029
|
+
return if @shared_connection
|
|
2030
|
+
|
|
2031
|
+
Pgbus.logger.warn do
|
|
2032
|
+
"[Pgbus::Client] Dropping pooled connections before the last stale-connection retry " \
|
|
2033
|
+
"(attempt #{attempt}/#{STALE_RETRY_ATTEMPTS}): #{error.message}"
|
|
2034
|
+
end
|
|
2035
|
+
{ "job" => @pgmq, "streams" => @streams_pool }.each do |name, pool|
|
|
2036
|
+
pool.reload
|
|
2037
|
+
rescue StandardError => e
|
|
2038
|
+
Pgbus.logger.warn do
|
|
2039
|
+
"[Pgbus::Client] #{name} pool reload before the last stale-connection retry failed: " \
|
|
2040
|
+
"#{e.class}: #{e.message}"
|
|
2041
|
+
end
|
|
2042
|
+
end
|
|
2043
|
+
end
|
|
2044
|
+
|
|
2010
2045
|
# True when libpq's connection-baked read bounds (statement_timeout +
|
|
2011
2046
|
# tcp_user_timeout + keepalives) fully cover both a slow query AND a
|
|
2012
2047
|
# dead/hung socket, so with_read_timeout can skip the Ruby Timeout entirely.
|
|
@@ -2293,6 +2328,7 @@ module Pgbus
|
|
|
2293
2328
|
"[Pgbus::Client] Retrying after stale pgmq connection " \
|
|
2294
2329
|
"(attempt #{attempts}/#{STALE_RETRY_ATTEMPTS}): #{e.message}"
|
|
2295
2330
|
end
|
|
2331
|
+
reload_pools_before_last_stale_retry(attempts, e) if attempts == STALE_RETRY_ATTEMPTS
|
|
2296
2332
|
retry
|
|
2297
2333
|
end
|
|
2298
2334
|
end
|
data/lib/pgbus/version.rb
CHANGED