Commit 8b4a553eee

8b4a553eee7c5689aad198d0e8400ef9444e2f29

parent: 3035725993

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 22:49 UTC

config: pack_queue = -1 leaves the queue unbounded

Ref #262

Layout: unified · split

internal/config/config.go +8 −3
@@ -7,6 +7,7 @@ import (
7 "encoding/pem" 7 "encoding/pem"
8 "errors" 8 "errors"
9 "fmt" 9 "fmt"
10 "math"
10 "net" 11 "net"
11 "os" 12 "os"
12 "path/filepath" 13 "path/filepath"
@@ -236,7 +237,8 @@ type Limits struct {
236} 237}
237 238
238// PackLimits resolves the pack_* settings for packlimit.New. A zero 239// PackLimits resolves the pack_* settings for packlimit.New. A zero
239// count is no bound. 240// max or per is no bound; an unbounded queue is math.MaxInt, since
241// packlimit reads a zero queue as no queue at all.
240func (l Limits) PackLimits() (max, per, queue int, wait time.Duration) { 242func (l Limits) PackLimits() (max, per, queue int, wait time.Duration) {
241 pick := func(v, def int) int { 243 pick := func(v, def int) int {
242 switch { 244 switch {
@@ -247,13 +249,16 @@ func (l Limits) PackLimits() (max, per, queue int, wait time.Duration) {
247 } 249 }
248 return v 250 return v
249 } 251 }
252 queue = pick(l.PackQueue, DefaultPackQueue)
253 if l.PackQueue < 0 {
254 queue = math.MaxInt
255 }
250 wait = DefaultPackQueueWait 256 wait = DefaultPackQueueWait
251 if d, err := time.ParseDuration(l.PackQueueWait); err == nil && d > 0 { 257 if d, err := time.ParseDuration(l.PackQueueWait); err == nil && d > 0 {
252 wait = d 258 wait = d
253 } 259 }
254 return pick(l.PackConcurrency, DefaultPackConcurrency), 260 return pick(l.PackConcurrency, DefaultPackConcurrency),
255 pick(l.PackPerPrincipal, DefaultPackPerPrincipal), 261 pick(l.PackPerPrincipal, DefaultPackPerPrincipal), queue, wait
256 pick(l.PackQueue, DefaultPackQueue), wait
257} 262}
258 263
259type Mail struct { 264type Mail struct {
internal/config/config_test.go +16 −15
@@ -6,6 +6,7 @@ import (
6 "crypto/rand" 6 "crypto/rand"
7 "crypto/x509" 7 "crypto/x509"
8 "encoding/pem" 8 "encoding/pem"
9 "math"
9 "os" 10 "os"
10 "path/filepath" 11 "path/filepath"
11 "strings" 12 "strings"
@@ -47,6 +48,21 @@ func TestLoadMinimal(t *testing.T) {
47 } 48 }
48} 49}
49 50
51func TestPackLimits(t *testing.T) {
52 max, per, queue, wait := Limits{}.PackLimits()
53 if max != DefaultPackConcurrency || per != DefaultPackPerPrincipal || queue != DefaultPackQueue || wait != DefaultPackQueueWait {
54 t.Fatalf("defaults: %d %d %d %s", max, per, queue, wait)
55 }
56 max, per, queue, wait = Limits{PackConcurrency: -1, PackPerPrincipal: -1, PackQueue: -1, PackQueueWait: "5s"}.PackLimits()
57 if max != 0 || per != 0 || queue != math.MaxInt || wait != 5*time.Second {
58 t.Fatalf("off: %d %d %d %s", max, per, queue, wait)
59 }
60 max, per, queue, _ = Limits{PackConcurrency: 8, PackPerPrincipal: 3, PackQueue: 64}.PackLimits()
61 if max != 8 || per != 3 || queue != 64 {
62 t.Fatalf("set: %d %d %d", max, per, queue)
63 }
64}
65
50func TestContradictions(t *testing.T) { 66func TestContradictions(t *testing.T) {
51 cases := []struct { 67 cases := []struct {
52 name string 68 name string
@@ -395,18 +411,3 @@ func TestBackupRecipients(t *testing.T) {
395 t.Fatalf("default: %v, %v", cfg.Backup, err) 411 t.Fatalf("default: %v, %v", cfg.Backup, err)
396 } 412 }
397} 413}
398
399func TestPackLimits(t *testing.T) {
400 max, per, queue, wait := Limits{}.PackLimits()
401 if max != DefaultPackConcurrency || per != DefaultPackPerPrincipal || queue != DefaultPackQueue || wait != DefaultPackQueueWait {
402 t.Fatalf("defaults: %d %d %d %s", max, per, queue, wait)
403 }
404 max, per, queue, wait = Limits{PackConcurrency: -1, PackPerPrincipal: -1, PackQueue: -1, PackQueueWait: "5s"}.PackLimits()
405 if max != 0 || per != 0 || queue != 0 || wait != 5*time.Second {
406 t.Fatalf("off: %d %d %d %s", max, per, queue, wait)
407 }
408 max, per, queue, _ = Limits{PackConcurrency: 8, PackPerPrincipal: 3, PackQueue: 64}.PackLimits()
409 if max != 8 || per != 3 || queue != 64 {
410 t.Fatalf("set: %d %d %d", max, per, queue)
411 }
412}
internal/packlimit/packlimit_test.go +15
@@ -2,6 +2,7 @@ package packlimit
2 2
3import ( 3import (
4 "errors" 4 "errors"
5 "math"
5 "testing" 6 "testing"
6 "time" 7 "time"
7) 8)
@@ -207,3 +208,17 @@ func waitQueued(t *testing.T, l *Limiter, n int) {
207 } 208 }
208 t.Fatalf("queue never reached %d", n) 209 t.Fatalf("queue never reached %d", n)
209} 210}
211
212// config maps pack_queue = -1 to math.MaxInt: waiters are not turned
213// away for want of queue room.
214func TestUnboundedQueue(t *testing.T) {
215 l := New(1, 0, math.MaxInt, 5*time.Second)
216 r1, _ := l.Acquire(nil, "a")
217 done := make(chan struct{})
218 defer close(done)
219 for i := 0; i < 64; i++ {
220 go l.Acquire(done, "b")
221 }
222 waitQueued(t, l, 64)
223 r1()
224}