pgbus 0.5.0 → 0.5.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 +15 -0
- data/lib/pgbus/configuration/capsule_dsl.rb +6 -20
- data/lib/pgbus/configuration.rb +61 -14
- 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: bb5b86968b21bebedf9b33d6d69338b86f7dfb5304b5efa714b270bc8128fa10
|
|
4
|
+
data.tar.gz: f5d6118149ebcd7c7f773ab399fbdc97079883dd895339c5918c156fc416bbfe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7649ee8dfd9d9ff155e510c014a28fb070d79357d34ed4e8a8c0369c502c90b806d01775a0b01f480709fa9399d7ecc3edddb7d4e092813d42014d412833d9ce
|
|
7
|
+
data.tar.gz: 756c31dd3fd970dca11c78cbb17af8b74e798cd42e3782b9993e4081982cc340a1224b6c0b829c259ea6d13abcec035510c2b4e0e6b8c39f73a34b1b9cd2bc48
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [0.5.1] - 2026-04-08
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **Capsule DSL: anonymous duplicate capsules are now allowed.** Configurations like `c.workers = "*: 3; *: 3; *: 3; *: 3; *: 3"` (the legacy YAML pattern of 5 forks × 3 threads, all reading every queue) were rejected at boot in 0.5.0 with `Pgbus::Configuration::CapsuleDSL::ParseError: wildcard '*' appears in two capsules`. PGMQ tolerates multiple processes reading the same queue natively (`FOR UPDATE SKIP LOCKED`), and this is the canonical way to scale CPU parallelism across forks, so the rejection was wrong.
|
|
6
|
+
|
|
7
|
+
The fix introduces a "named vs anonymous" distinction:
|
|
8
|
+
|
|
9
|
+
- The string DSL parser is now purely syntactic — it no longer enforces overlap rules.
|
|
10
|
+
- `Pgbus::Configuration#workers=` auto-assigns `:name` only to capsules whose first queue would yield a *unique* name AND is not the bare wildcard. Wildcards stay anonymous; collision-prone first-queues stay anonymous.
|
|
11
|
+
- `Pgbus::Configuration#validate_no_queue_overlap!` (called by `c.capsule :name, ...`) now only checks against existing **named** capsules. Anonymous capsules can overlap freely with each other and with named capsules.
|
|
12
|
+
- Net result: `"*: 3; *: 3; *: 3"` produces 3 anonymous capsules (3 forks), `"critical: 5; default: 10"` produces 2 named capsules (CLI `--capsule critical` still works), and named-vs-named overlap is still rejected as before.
|
|
13
|
+
|
|
14
|
+
No changes required to user configuration — legacy YAML patterns and the modern DSL both work as documented.
|
|
15
|
+
|
|
1
16
|
## [Unreleased]
|
|
2
17
|
|
|
3
18
|
### Breaking Changes
|
|
@@ -62,9 +62,12 @@ module Pgbus
|
|
|
62
62
|
validate_input_type!
|
|
63
63
|
validate_input_not_empty!
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
capsules
|
|
65
|
+
# Pure tokenization: split, parse each capsule, return them in order.
|
|
66
|
+
# Cross-capsule overlap rules live in Pgbus::Configuration#workers=
|
|
67
|
+
# because they depend on whether the resulting capsules are named or
|
|
68
|
+
# anonymous, and naming is a Configuration concern (not a parser one).
|
|
69
|
+
# Within-capsule duplicate-queue checks still happen in parse_capsule.
|
|
70
|
+
split_capsules(@input).map { |segment| parse_capsule(segment) }
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
private
|
|
@@ -168,23 +171,6 @@ module Pgbus
|
|
|
168
171
|
seen[q] = true
|
|
169
172
|
end
|
|
170
173
|
end
|
|
171
|
-
|
|
172
|
-
def validate_no_duplicate_queues_across_capsules!(capsules)
|
|
173
|
-
return if capsules.size < 2
|
|
174
|
-
|
|
175
|
-
seen = {}
|
|
176
|
-
capsules.each_with_index do |capsule, idx|
|
|
177
|
-
capsule[:queues].each do |q|
|
|
178
|
-
if seen[q]
|
|
179
|
-
label = (q == WILDCARD ? "wildcard '*'" : "queue #{q.inspect}")
|
|
180
|
-
raise ParseError,
|
|
181
|
-
"#{label} appears in two capsules (positions #{seen[q]} and #{idx + 1}) " \
|
|
182
|
-
"in #{@input.inspect} — each queue can only be assigned to one capsule"
|
|
183
|
-
end
|
|
184
|
-
seen[q] = idx + 1
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
174
|
end
|
|
189
175
|
end
|
|
190
176
|
end
|
data/lib/pgbus/configuration.rb
CHANGED
|
@@ -230,12 +230,34 @@ module Pgbus
|
|
|
230
230
|
# dispatcher-only processes).
|
|
231
231
|
#
|
|
232
232
|
# Raises ArgumentError for any other type.
|
|
233
|
+
#
|
|
234
|
+
# NAMING SEMANTICS for the String form:
|
|
235
|
+
#
|
|
236
|
+
# The parser produces anonymous capsules (no :name). The setter then
|
|
237
|
+
# auto-assigns a :name to capsules whose first queue would yield a
|
|
238
|
+
# *unique* name across the parsed list AND is not the bare wildcard
|
|
239
|
+
# (`*`). Anything else stays anonymous.
|
|
240
|
+
#
|
|
241
|
+
# "critical: 5; default: 10" -> two NAMED capsules ("critical", "default")
|
|
242
|
+
# "*: 5" -> one anonymous capsule (wildcard never names)
|
|
243
|
+
# "*: 3; *: 3; *: 3" -> three anonymous capsules — legal,
|
|
244
|
+
# represents "3 forks all reading every
|
|
245
|
+
# queue", restoring the legacy YAML
|
|
246
|
+
# `5 × {queues: ["*"], threads: 3}` shape
|
|
247
|
+
# "default: 5; default: 3" -> two anonymous capsules — same logic
|
|
248
|
+
#
|
|
249
|
+
# The point of the carve-out is the legacy "I want N forks of the same
|
|
250
|
+
# worker pool" pattern: it must keep working since PGMQ tolerates it
|
|
251
|
+
# natively (multiple processes reading the same queue with FOR UPDATE
|
|
252
|
+
# SKIP LOCKED). The CLI's --capsule selector only matches NAMED
|
|
253
|
+
# capsules, so anonymous duplicates can't be ambiguously addressed.
|
|
233
254
|
def workers=(value)
|
|
234
255
|
@workers = case value
|
|
235
256
|
when nil
|
|
236
257
|
nil
|
|
237
258
|
when String
|
|
238
|
-
CapsuleDSL.parse(value)
|
|
259
|
+
parsed = CapsuleDSL.parse(value)
|
|
260
|
+
assign_auto_names(parsed)
|
|
239
261
|
when Array
|
|
240
262
|
value
|
|
241
263
|
else
|
|
@@ -445,33 +467,58 @@ module Pgbus
|
|
|
445
467
|
raw&.to_s
|
|
446
468
|
end
|
|
447
469
|
|
|
448
|
-
#
|
|
449
|
-
#
|
|
450
|
-
#
|
|
451
|
-
#
|
|
470
|
+
# Auto-assign :name to parsed capsules where the first queue token would
|
|
471
|
+
# yield a unique name and is not the bare wildcard. See the long comment
|
|
472
|
+
# on +workers=+ for the why. Returns the same array with :name merged in
|
|
473
|
+
# where applicable.
|
|
474
|
+
def assign_auto_names(parsed_capsules)
|
|
475
|
+
first_queue_counts = parsed_capsules.each_with_object(Hash.new(0)) do |capsule, h|
|
|
476
|
+
h[capsule[:queues].first] += 1
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
parsed_capsules.map do |capsule|
|
|
480
|
+
first = capsule[:queues].first
|
|
481
|
+
nameable = first != CapsuleDSL::WILDCARD && first_queue_counts[first] == 1
|
|
482
|
+
nameable ? capsule.merge(name: first.to_s) : capsule
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Validates that the new capsule (added via +c.capsule :name, ...+) does
|
|
487
|
+
# not overlap with any existing NAMED capsule. Anonymous capsules (parsed
|
|
488
|
+
# from the string DSL with auto-naming skipped, e.g. wildcards or
|
|
489
|
+
# would-collide first-queues) are intentionally invisible here — they
|
|
490
|
+
# represent "N forks of the same pool" and are allowed to overlap with
|
|
491
|
+
# each other and with named capsules.
|
|
492
|
+
#
|
|
493
|
+
# The wildcard '*' counts as overlapping with EVERY other queue (and
|
|
494
|
+
# vice versa) because at runtime '*' is expanded to all known queues.
|
|
495
|
+
# Raises ArgumentError on overlap.
|
|
452
496
|
def validate_no_queue_overlap!(new_queues)
|
|
453
|
-
|
|
454
|
-
return if
|
|
497
|
+
existing_named = (@workers || []).select { |c| capsule_name(c) }
|
|
498
|
+
return if existing_named.empty?
|
|
499
|
+
|
|
500
|
+
existing_queues = existing_named.flat_map { |c| c[:queues] || c["queues"] || [] }
|
|
501
|
+
return if existing_queues.empty?
|
|
455
502
|
|
|
456
|
-
if
|
|
503
|
+
if existing_queues.include?(CapsuleDSL::WILDCARD)
|
|
457
504
|
raise ArgumentError,
|
|
458
|
-
"an existing capsule already uses '*' (matches every queue) — " \
|
|
505
|
+
"an existing named capsule already uses '*' (matches every queue) — " \
|
|
459
506
|
"the new capsule's queues #{new_queues.inspect} would overlap with it"
|
|
460
507
|
end
|
|
461
508
|
|
|
462
509
|
if new_queues.include?(CapsuleDSL::WILDCARD)
|
|
463
510
|
raise ArgumentError,
|
|
464
|
-
"the new capsule uses '*' (matches every queue) but other capsules " \
|
|
465
|
-
"are already defined with queues #{
|
|
511
|
+
"the new capsule uses '*' (matches every queue) but other named capsules " \
|
|
512
|
+
"are already defined with queues #{existing_queues.inspect} — " \
|
|
466
513
|
"the wildcard would overlap with all of them"
|
|
467
514
|
end
|
|
468
515
|
|
|
469
|
-
conflict = new_queues.find { |q|
|
|
516
|
+
conflict = new_queues.find { |q| existing_queues.include?(q) }
|
|
470
517
|
return unless conflict
|
|
471
518
|
|
|
472
519
|
raise ArgumentError,
|
|
473
|
-
"queue #{conflict.inspect} is already assigned to another capsule — " \
|
|
474
|
-
"
|
|
520
|
+
"queue #{conflict.inspect} is already assigned to another named capsule — " \
|
|
521
|
+
"named capsules cannot share queues"
|
|
475
522
|
end
|
|
476
523
|
|
|
477
524
|
def sum_thread_counts(entries, default_threads:, group:)
|
data/lib/pgbus/version.rb
CHANGED