pgbus 0.11.1 → 0.11.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/CHANGELOG.md +1 -0
- data/README.md +19 -0
- data/lib/pgbus/cli.rb +24 -0
- data/lib/pgbus/configuration.rb +36 -0
- data/lib/pgbus/doctor.rb +74 -15
- data/lib/pgbus/process/supervisor.rb +35 -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: 727dd6a7515414a2a426857ea19cc8cf33b8c5ca2311ecde116bf643114e8d85
|
|
4
|
+
data.tar.gz: c68f0e3c246b554059160e901e96d61f80c5a5a9aa1716d6e4787f84cfdbe655
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f515f94c6e585a038ab468ca30dc49a284c9666ddb0116998532ce2afdf602ac8309972fbe79606ad055b73bd95da418210778cf80a9e3062288855d67a3b746
|
|
7
|
+
data.tar.gz: dd748949d2330236bfbc46b94c04ff907268e067830a2c4b91b3ff6b811324b2383d3c46bc950add9511c98355b39b39d9f3a58c03a3fed361838c866b24bf1c
|
data/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
|
+
- **`config.doctor_on_boot` (+ `pgbus start --doctor` / `--doctor-strict`) — run the doctor preflight inside the booting supervisor, one Rails boot instead of two (issue #347).** The recommended deploy preflight was `bin/pgbus doctor || true` in the container entrypoint before `bin/pgbus start` — but both commands `require config/environment`, so the job container boots the full Rails app **twice** on every deploy (cost scaling with app boot time), and running doctor pre-supervisor makes the `Process liveness` check false-fail (no workers exist yet), which is exactly why the entrypoint has to swallow the exit code with `|| true` — losing any chance to gate on a genuinely-fatal finding. Now `config.doctor_on_boot = :report` (or `pgbus start --doctor`) runs the checks in the already-booted supervisor, after the DB is verified reachable and queues are bootstrapped but **before any worker is forked**: one boot, and the worker-dependent `Process liveness` check is skipped (it has no workers to observe yet and would false-fail on stale prior-generation rows during a redeploy). `config.doctor_on_boot = :strict` (or `--doctor-strict`) additionally **refuses to boot** — raising before forking anything, so the `ensure shutdown` path tears down the heartbeat/health server and no child starts — but *only* on a genuinely-fatal, non-transient check: a `Configuration` failure (a real config bug) or an **absent** PGMQ schema. It deliberately does **not** abort on `Queues`/`Database` failures — those are the transients the lenient queue bootstrap is built to ride out (children crash-and-backoff until the DB recovers), and `verify_connection!` already gated a hard-down DB moments earlier; making them strict-fatal would take down a whole fleet's cold boot in lockstep on a momentarily-saturated primary. Default `nil`/`false` = off (byte-identical to today; the supervisor constructs no `Doctor` and does zero extra work). The standalone `pgbus doctor` command and its exit-code semantics are unchanged. Refs #347.
|
|
13
14
|
- **`Pgbus::Streams::PhlexHelpers` — a Phlex-includable `pgbus_stream_from`, and `Pgbus.stream_name_budget` (issue #334).** Two small helpers that stop consuming apps reimplementing pgbus glue. (1) `pgbus_stream_from` is a Rails view helper, so a Phlex component couldn't call it without hand-registering an output-helper macro (phlex-rails ships no `Phlex::Rails::Helpers::PgbusStreamFrom`). `include Pgbus::Streams::PhlexHelpers` now bridges it exactly like phlex-rails' own `TurboStreamFrom`. phlex-rails stays an **optional** dependency — the module references its `HelperMacros` and is loaded on demand (`require "pgbus/streams/phlex_helpers"`), never eagerly. (2) `Pgbus.stream_name_budget` exposes the maximum stream-name length (the pgmq queue-name cap minus the queue prefix) so apps size/truncate stream identifiers up front instead of hand-computing `MAX_QUEUE_NAME_LENGTH - queue_prefix.length - 1` at every call site. Refs #334.
|
|
14
15
|
- **`EventBus::Registry#setup_all!(safe: true)` — a boot/rake-safe way to eagerly set up event subscribers (issue #334).** Calling `setup_all!` opens a PGMQ connection per subscriber (to create its queue + bind its topic), which is wrong during a `db:`/schema rake task (a live connection blocks `DROP DATABASE`) and crashes boot if the database isn't up yet — so apps wrapped it in a hand-rolled multi-class rescue plus a rake-task skip. `setup_all!(safe: true)` bakes that in: it skips entirely in a schema/asset rake context and swallows a connection error with a warning instead of raising. The default `setup_all!` is unchanged (raises). Refs #334.
|
|
15
16
|
- **Connection-pooler safety (`config.require_primary`, `config.connection_guc_mode`) — pgbus is now correct-and-loud behind a transaction/read-write-splitting pooler instead of silently stalling (issue #332).** Three linked fixes. (1) **Primary affinity:** a read/write-splitting pooler (pgdog/pgcat) can route pgmq's VOLATILE `read`/`archive`/`delete` to a read replica, where workers read nothing (`read_ct=0`) and jobs stop with a **healthy heartbeat** — the supervisor watchdog keys on loop advancement, not read progress, so nothing surfaced the stall. The job read path had zero primary-affinity defense (only the LISTEN/streamer paths validated the primary via `PrimaryValidator`). Setting `config.require_primary = true` (default **false** — a single-primary deployment is byte-identical) makes `verify_connection!` reject a connection that lands on a replica (`pg_is_in_recovery() => t`) at supervisor boot, raising a clear `ConfigurationError` instead of forking children that read nothing. (2) **GUC forwarding:** pgbus's AR-config extraction dropped database.yml's `variables:` block, so `client_min_messages` (and any GUC) never reached pgmq's dedicated raw connections (NOTICE flooding that three consuming apps hand-patched via `connection_params`). GUCs are now forwarded, on both the AR-extracted and explicit-`connection_params` paths, by `config.connection_guc_mode`: `:options` (default) bakes them into the libpq `options` STARTUP param (byte-compatible with today for the read-timeout `statement_timeout`, now also carrying `variables:`), while `:session` applies them via post-connect `SET` on a fresh connection instead — for a transaction-mode PgBouncer that **rejects** the `options` startup param with a FATAL. `pgbus doctor` gains a ninth check ("Primary affinity") that warns (never fails) when the job connection is currently on a replica, naming the direct-port remediation. DB-gated integration tests prove both GUC modes round-trip a forwarded GUC and that `require_primary` verifies on a real primary. Refs #332.
|
data/README.md
CHANGED
|
@@ -1872,6 +1872,25 @@ rake pgbus:doctor # identical checks, for a Rake-based deploy pipeline
|
|
|
1872
1872
|
|
|
1873
1873
|
The report ends with a resolved-config summary (queue prefix, pool size, roles, capsules) with any password redacted from `database_url` / `connection_params`. Warnings do not fail the exit code — only a `:fail` result does.
|
|
1874
1874
|
|
|
1875
|
+
##### Doctor preflight at boot (single Rails boot)
|
|
1876
|
+
|
|
1877
|
+
Running `pgbus doctor || true` in a container entrypoint before `pgbus start` boots the full Rails app **twice** on every deploy, and the pre-supervisor `Process liveness` check false-fails (no workers exist yet) — which is why the `|| true` is needed, swallowing any genuinely-fatal finding too. Instead, run the preflight *inside* the booting supervisor:
|
|
1878
|
+
|
|
1879
|
+
```bash
|
|
1880
|
+
pgbus start --doctor # run the preflight, log the report, always boot
|
|
1881
|
+
pgbus start --doctor-strict # refuse to boot on a fatal check (exit non-zero, fork nothing)
|
|
1882
|
+
```
|
|
1883
|
+
|
|
1884
|
+
or set it in the initializer:
|
|
1885
|
+
|
|
1886
|
+
```ruby
|
|
1887
|
+
Pgbus.configure do |c|
|
|
1888
|
+
c.doctor_on_boot = :report # or :strict; nil/false (default) is off
|
|
1889
|
+
end
|
|
1890
|
+
```
|
|
1891
|
+
|
|
1892
|
+
The boot preflight runs after the DB is verified reachable and queues are bootstrapped, but **before any worker is forked** — one Rails boot, and the `Process liveness` check is skipped (nothing to observe yet, so it can't false-fail). `:strict` aborts the boot only on a genuinely-fatal, non-transient check — a `Configuration` failure or an **absent** PGMQ schema. A transient DB blip that the lenient queue bootstrap is designed to ride out (surfacing as a `Queues` or `Database` failure) is reported but never aborts, so a fleet-wide cold boot against a momentarily-saturated primary doesn't fail every pod in lockstep.
|
|
1893
|
+
|
|
1875
1894
|
#### pgbus dlq
|
|
1876
1895
|
|
|
1877
1896
|
Dead-letter inspect/drain operations from a headless deployment or incident runbook, routed through `Web::DataSource` so retry/discard semantics are identical to the dashboard (origin-queue re-enqueue, transactional produce+delete, lock release on discard) with zero raw SQL and no direct PGMQ calls:
|
data/lib/pgbus/cli.rb
CHANGED
|
@@ -51,6 +51,16 @@ module Pgbus
|
|
|
51
51
|
Pgbus.configuration.execution_mode = options[:execution_mode].to_sym if options[:execution_mode]
|
|
52
52
|
apply_capsule_filter(options[:capsule]) if options[:capsule]
|
|
53
53
|
apply_role_filter(options)
|
|
54
|
+
apply_doctor_flag(options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# --doctor runs the boot preflight in report mode; --doctor-strict aborts
|
|
58
|
+
# the boot on a fatal finding. Both set config.doctor_on_boot, which the
|
|
59
|
+
# supervisor reads at boot (issue #347). Strict wins if both are passed — it
|
|
60
|
+
# is the stronger gate, so a user asking for both clearly wants strict.
|
|
61
|
+
def apply_doctor_flag(options)
|
|
62
|
+
Pgbus.configuration.doctor_on_boot = :report if options[:doctor]
|
|
63
|
+
Pgbus.configuration.doctor_on_boot = :strict if options[:doctor_strict]
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def parse_start_options(args)
|
|
@@ -81,6 +91,14 @@ module Pgbus
|
|
|
81
91
|
opts.on("--execution-mode MODE", "Execution mode: threads (default) or async") do |v|
|
|
82
92
|
options[:execution_mode] = v
|
|
83
93
|
end
|
|
94
|
+
|
|
95
|
+
opts.on("--doctor", "Run doctor preflight checks at boot and log the report (issue #347)") do
|
|
96
|
+
options[:doctor] = true
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
opts.on("--doctor-strict", "Run doctor preflight and refuse to boot on a fatal finding") do
|
|
100
|
+
options[:doctor_strict] = true
|
|
101
|
+
end
|
|
84
102
|
end.parse!(args.dup)
|
|
85
103
|
options
|
|
86
104
|
end
|
|
@@ -199,6 +217,12 @@ module Pgbus
|
|
|
199
217
|
pattern)
|
|
200
218
|
--execution-mode Execution mode: threads (default) or async
|
|
201
219
|
(fiber-based, lower connection usage)
|
|
220
|
+
--doctor Run doctor preflight checks in the booting
|
|
221
|
+
supervisor and log the report (one Rails boot
|
|
222
|
+
instead of `pgbus doctor` + `pgbus start`)
|
|
223
|
+
--doctor-strict Like --doctor, but refuse to boot (exit non-zero,
|
|
224
|
+
fork nothing) if a fatal check fails (bad config
|
|
225
|
+
or an absent PGMQ schema)
|
|
202
226
|
|
|
203
227
|
Environment for `mcp`:
|
|
204
228
|
PGBUS_MCP_TOKEN If set, PGBUS_MCP_AUTH_TOKEN must match for
|
data/lib/pgbus/configuration.rb
CHANGED
|
@@ -89,6 +89,19 @@ module Pgbus
|
|
|
89
89
|
# PGMQ schema installation mode (:auto, :extension, :embedded)
|
|
90
90
|
attr_reader :pgmq_schema_mode
|
|
91
91
|
|
|
92
|
+
# Run doctor preflight checks inside the supervisor at boot (issue #347),
|
|
93
|
+
# so an entrypoint need not boot Rails twice (once for `pgbus doctor`, once
|
|
94
|
+
# for `pgbus start`). nil/false (default) — off, byte-identical to today.
|
|
95
|
+
# :report — run the preflight and log the report, always continue booting.
|
|
96
|
+
# :strict — additionally REFUSE to boot (raise before forking any worker)
|
|
97
|
+
# when a genuinely-fatal, non-transient check fails. The strict
|
|
98
|
+
# set is deliberately narrow (Configuration, PGMQ schema) so a
|
|
99
|
+
# transient boot-time DB blip — which the lenient queue bootstrap
|
|
100
|
+
# is designed to ride out via child crash-and-backoff — never
|
|
101
|
+
# aborts a whole fleet's cold boot in lockstep. Set via the
|
|
102
|
+
# `--doctor` / `--doctor-strict` start flags too.
|
|
103
|
+
attr_reader :doctor_on_boot
|
|
104
|
+
|
|
92
105
|
# Event consumers
|
|
93
106
|
attr_reader :event_consumers
|
|
94
107
|
|
|
@@ -248,6 +261,7 @@ module Pgbus
|
|
|
248
261
|
@worker_notify_database_url = nil
|
|
249
262
|
|
|
250
263
|
@pgmq_schema_mode = :auto
|
|
264
|
+
@doctor_on_boot = nil
|
|
251
265
|
|
|
252
266
|
@event_consumers = nil
|
|
253
267
|
|
|
@@ -566,6 +580,28 @@ module Pgbus
|
|
|
566
580
|
@pgmq_schema_mode = mode
|
|
567
581
|
end
|
|
568
582
|
|
|
583
|
+
VALID_DOCTOR_ON_BOOT_MODES = [nil, :report, :strict].freeze
|
|
584
|
+
|
|
585
|
+
# nil/false are both "off"; a String is coerced to a Symbol so the CLI flags
|
|
586
|
+
# and YAML-ish configs work. Validated at assignment time (like the other
|
|
587
|
+
# enum options), not in validate!.
|
|
588
|
+
def doctor_on_boot=(mode)
|
|
589
|
+
coerced = case mode
|
|
590
|
+
when nil, false then nil
|
|
591
|
+
when Symbol then mode
|
|
592
|
+
when String then mode.to_sym
|
|
593
|
+
else
|
|
594
|
+
raise Pgbus::ConfigurationError,
|
|
595
|
+
"Invalid doctor_on_boot type: #{mode.class}. Must be nil, false, String, or Symbol"
|
|
596
|
+
end
|
|
597
|
+
unless VALID_DOCTOR_ON_BOOT_MODES.include?(coerced)
|
|
598
|
+
raise Pgbus::ConfigurationError,
|
|
599
|
+
"Invalid doctor_on_boot: #{coerced.inspect}. Must be nil/false (off), :report, or :strict"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
@doctor_on_boot = coerced
|
|
603
|
+
end
|
|
604
|
+
|
|
569
605
|
def validate!
|
|
570
606
|
if pool_size && !(pool_size.is_a?(Numeric) && pool_size.positive?)
|
|
571
607
|
raise Pgbus::ConfigurationError, "pool_size must be a positive number or nil (auto-tune)"
|
data/lib/pgbus/doctor.rb
CHANGED
|
@@ -25,26 +25,47 @@ module Pgbus
|
|
|
25
25
|
|
|
26
26
|
STATUS_ICON = { ok: "✓", warn: "!", fail: "✗" }.freeze
|
|
27
27
|
|
|
28
|
+
# The ordered check suite, name → method. The name strings are the public,
|
|
29
|
+
# stable identity of each check (used in the report and to select subsets).
|
|
30
|
+
CHECKS = {
|
|
31
|
+
"Configuration" => :check_configuration,
|
|
32
|
+
"Database" => :check_database,
|
|
33
|
+
"PGMQ schema" => :check_pgmq_schema,
|
|
34
|
+
"Queues" => :check_queues,
|
|
35
|
+
"LISTEN/NOTIFY" => :check_notify,
|
|
36
|
+
"Process liveness" => :check_processes,
|
|
37
|
+
"GlobalID allowlist" => :check_allowed_global_id_models,
|
|
38
|
+
"Broadcast queue" => :check_broadcast_queue,
|
|
39
|
+
"Primary affinity" => :check_primary
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# Process liveness reads the pgbus_processes table (via HealthAnalyzer), so
|
|
43
|
+
# it is only meaningful once workers have registered. The supervisor boot
|
|
44
|
+
# preflight (issue #347) runs BEFORE forking any worker, so it excludes this
|
|
45
|
+
# check — otherwise stale prior-generation worker rows plus a visible backlog
|
|
46
|
+
# would produce a false STALLED verdict on a redeploy.
|
|
47
|
+
BOOT_SKIP = ["Process liveness"].freeze
|
|
48
|
+
|
|
49
|
+
# The subset of checks whose :fail is genuinely deploy-fatal AND not a
|
|
50
|
+
# transient the supervisor is designed to ride out. Only these abort a
|
|
51
|
+
# `:strict` boot. Configuration#validate! failing is a real config bug;
|
|
52
|
+
# an absent PGMQ schema means the migrations never ran. Deliberately NOT
|
|
53
|
+
# Queues/Database: the lenient queue bootstrap swallows a boot-time DB blip
|
|
54
|
+
# so children crash-and-backoff and recover, and verify_connection! already
|
|
55
|
+
# gated a hard-down DB before the preflight — making either strict-fatal
|
|
56
|
+
# would turn a tolerated transient into a fleet-wide lockstep boot abort.
|
|
57
|
+
STRICT_FATAL = ["Configuration", "PGMQ schema"].freeze
|
|
58
|
+
|
|
28
59
|
def initialize(config: Pgbus.configuration, client: Pgbus.client, data_source: nil)
|
|
29
60
|
@config = config
|
|
30
61
|
@client = client
|
|
31
|
-
@data_source = data_source
|
|
62
|
+
@data_source = data_source
|
|
32
63
|
end
|
|
33
64
|
|
|
34
65
|
# Run all checks and return an array of result hashes:
|
|
35
66
|
# { name:, status: :ok|:warn|:fail, detail: }
|
|
36
67
|
def run
|
|
37
|
-
@run ||=
|
|
38
|
-
check_configuration,
|
|
39
|
-
check_database,
|
|
40
|
-
check_pgmq_schema,
|
|
41
|
-
check_queues,
|
|
42
|
-
check_notify,
|
|
43
|
-
check_processes,
|
|
44
|
-
check_allowed_global_id_models,
|
|
45
|
-
check_broadcast_queue,
|
|
46
|
-
check_primary
|
|
47
|
-
].map(&:to_h)
|
|
68
|
+
@run ||= run_checks(CHECKS.keys)
|
|
48
69
|
end
|
|
49
70
|
|
|
50
71
|
# True when no check failed. Warnings do not fail the run — they surface a
|
|
@@ -53,11 +74,34 @@ module Pgbus
|
|
|
53
74
|
run.none? { |c| c[:status] == :fail }
|
|
54
75
|
end
|
|
55
76
|
|
|
77
|
+
# --- Supervisor boot preflight (issue #347) ---
|
|
78
|
+
|
|
79
|
+
# The checks safe to run inside the booting supervisor before any worker is
|
|
80
|
+
# forked: everything except the worker-dependent process-liveness check.
|
|
81
|
+
# Runs a genuine SUBSET — check_processes is never invoked — so there is no
|
|
82
|
+
# pre-fork HealthAnalyzer/DataSource round-trip.
|
|
83
|
+
def boot_checks
|
|
84
|
+
@boot_checks ||= run_checks(CHECKS.keys - BOOT_SKIP)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# True unless a strict-fatal check (see STRICT_FATAL) failed. Warnings and
|
|
88
|
+
# transient-shaped failures (Queues, Database) never block a `:strict` boot.
|
|
89
|
+
def boot_ok?
|
|
90
|
+
boot_checks.none? { |c| c[:status] == :fail && STRICT_FATAL.include?(c[:name]) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Human-readable report for the boot preflight — the boot_checks subset.
|
|
94
|
+
def boot_report
|
|
95
|
+
report(boot_checks)
|
|
96
|
+
end
|
|
97
|
+
|
|
56
98
|
# Human-readable report: one line per check, then a resolved-config summary
|
|
57
99
|
# with passwords redacted. Suitable for stdout in the CLI and rake task.
|
|
58
|
-
|
|
100
|
+
# Defaults to the full run; pass a filtered result array (e.g. boot_checks)
|
|
101
|
+
# to render a subset.
|
|
102
|
+
def report(checks = run)
|
|
59
103
|
lines = ["Pgbus Doctor", "=" * 40]
|
|
60
|
-
|
|
104
|
+
checks.each do |check|
|
|
61
105
|
icon = STATUS_ICON.fetch(check[:status], "?")
|
|
62
106
|
lines << format("%<icon>s %<name>-22s %<detail>s", icon: icon, name: check[:name], detail: check[:detail])
|
|
63
107
|
end
|
|
@@ -86,6 +130,21 @@ module Pgbus
|
|
|
86
130
|
|
|
87
131
|
private
|
|
88
132
|
|
|
133
|
+
# Run the named checks in CHECKS order and return an array of result hashes.
|
|
134
|
+
# Only the requested checks are INVOKED — a check omitted from `names` does
|
|
135
|
+
# no work (e.g. boot_checks never calls check_processes, so no HealthAnalyzer
|
|
136
|
+
# round-trip pre-fork).
|
|
137
|
+
def run_checks(names)
|
|
138
|
+
CHECKS.filter_map { |name, method| send(method).to_h if names.include?(name) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The dashboard data source backing the process-liveness check. Built lazily
|
|
142
|
+
# so the boot preflight (which excludes that check) never constructs a
|
|
143
|
+
# DataSource or touches the dashboard layer at the fork boundary.
|
|
144
|
+
def data_source
|
|
145
|
+
@data_source ||= Pgbus::Web::DataSource.new(client: @client)
|
|
146
|
+
end
|
|
147
|
+
|
|
89
148
|
# True in a Rails production environment. Guarded so the doctor still runs
|
|
90
149
|
# outside Rails (plain Ruby, tests) without assuming Rails is loaded.
|
|
91
150
|
def production?
|
|
@@ -181,7 +240,7 @@ module Pgbus
|
|
|
181
240
|
# from the shared HealthAnalyzer. STALLED (the silent-worker-wedge) is a
|
|
182
241
|
# failure; DEGRADED is a warning; OK passes.
|
|
183
242
|
def check_processes
|
|
184
|
-
verdict = Pgbus::MCP::HealthAnalyzer.new(
|
|
243
|
+
verdict = Pgbus::MCP::HealthAnalyzer.new(data_source).verdict
|
|
185
244
|
status = verdict[:status]
|
|
186
245
|
detail = Array(verdict[:reasons]).first || status
|
|
187
246
|
|
|
@@ -76,6 +76,14 @@ module Pgbus
|
|
|
76
76
|
# notify_trigger_current? makes those calls cheap no-ops.
|
|
77
77
|
bootstrap_queues
|
|
78
78
|
|
|
79
|
+
# Optional in-process doctor preflight (issue #347): run the diagnostic
|
|
80
|
+
# checks here — after config is loaded, the DB is verified reachable, and
|
|
81
|
+
# queues are bootstrapped, but BEFORE any worker is forked — so an
|
|
82
|
+
# entrypoint gets a single Rails boot instead of `pgbus doctor` + `pgbus
|
|
83
|
+
# start`. :strict aborts the boot (raising, so nothing forks) on a
|
|
84
|
+
# genuinely-fatal finding; :report only logs. Off by default.
|
|
85
|
+
run_doctor_preflight unless config.doctor_on_boot.nil?
|
|
86
|
+
|
|
79
87
|
boot_processes
|
|
80
88
|
monitor_loop
|
|
81
89
|
ensure
|
|
@@ -683,6 +691,33 @@ module Pgbus
|
|
|
683
691
|
ErrorReporter.report(e, { action: "bootstrap_queues" })
|
|
684
692
|
end
|
|
685
693
|
|
|
694
|
+
# In-process doctor preflight (issue #347). Runs the boot-safe subset of
|
|
695
|
+
# doctor checks (everything except the worker-dependent process-liveness
|
|
696
|
+
# check, which has no workers to observe yet) against the supervisor's own
|
|
697
|
+
# config and the already-verified shared client, and logs the report.
|
|
698
|
+
#
|
|
699
|
+
# In :strict mode, a genuinely-fatal check (Doctor::STRICT_FATAL —
|
|
700
|
+
# Configuration or an absent PGMQ schema) aborts the boot by raising
|
|
701
|
+
# Pgbus::ConfigurationError. Because this runs before boot_processes, no
|
|
702
|
+
# child is forked, and `run`'s `ensure shutdown` tears down the heartbeat
|
|
703
|
+
# and health server — the same fail-fast path verify_connection! uses.
|
|
704
|
+
# A transient-shaped failure (Queues, Database) is reported but never
|
|
705
|
+
# aborts: the lenient bootstrap above is built to let children ride out a
|
|
706
|
+
# boot-time DB blip, and a strict abort there would take down a whole
|
|
707
|
+
# fleet's cold boot in lockstep.
|
|
708
|
+
def run_doctor_preflight
|
|
709
|
+
doctor = Pgbus::Doctor.new(config: config, client: Pgbus.client)
|
|
710
|
+
report = doctor.boot_report
|
|
711
|
+
Pgbus.logger.info { "[Pgbus] doctor preflight (#{config.doctor_on_boot}):\n#{report}" }
|
|
712
|
+
|
|
713
|
+
return unless config.doctor_on_boot == :strict
|
|
714
|
+
return if doctor.boot_ok?
|
|
715
|
+
|
|
716
|
+
raise Pgbus::ConfigurationError,
|
|
717
|
+
"doctor preflight failed a fatal check (doctor_on_boot: :strict) — refusing to boot. " \
|
|
718
|
+
"See the report above."
|
|
719
|
+
end
|
|
720
|
+
|
|
686
721
|
def load_rails_app
|
|
687
722
|
return unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
688
723
|
|
data/lib/pgbus/version.rb
CHANGED