Skip to content

Commit 664dc15

Browse files
authored
stable release: Merge pull request #11 from matveynator/tvey/set-system-limits-on-program-start
Fix per-OS rlimit handling
2 parents 61c5661 + e364b6c commit 664dc15

3 files changed

Lines changed: 197 additions & 5 deletions

File tree

pkg/limits/limits_posix_darwin.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//go:build darwin
2+
// +build darwin
3+
4+
// Package limits includes POSIX-specific limit tuning to mirror xinetd-like defaults on macOS.
5+
// Using a macOS-specific file keeps type handling aligned with the unsigned Rlimit fields.
6+
package limits
7+
8+
import (
9+
"fmt"
10+
"log"
11+
"syscall"
12+
)
13+
14+
// collectLimitRequests assembles the desired RLIMIT adjustments for macOS.
15+
// Keeping the list together documents which resources mirror the xinetd expectations.
16+
func collectLimitRequests(logger *log.Logger) []limitRequest {
17+
desiredOpenFiles := uint64(100000)
18+
desiredProcesses := uint64(100000)
19+
20+
requests := []limitRequest{
21+
buildInfinityRequest("virtual memory (rlimit_as)", syscall.RLIMIT_AS),
22+
buildInfinityRequest("CPU time (rlimit_cpu)", syscall.RLIMIT_CPU),
23+
buildTargetRequest("open files (rlimit_files)", syscall.RLIMIT_NOFILE, desiredOpenFiles, logger),
24+
}
25+
26+
if procResource, ok := processLimitResource(); ok {
27+
requests = append(requests, buildTargetRequest("process count (rlimit_proc)", procResource, desiredProcesses, logger))
28+
} else {
29+
logger.Printf("Process limit resource is unavailable on this platform; skipping rlimit_proc")
30+
}
31+
32+
return requests
33+
}
34+
35+
// buildInfinityRequest raises a resource to the platform infinity constant.
36+
// Using RLIM_INFINITY avoids unsafe conversions across architectures.
37+
func buildInfinityRequest(label string, resource int) limitRequest {
38+
return limitRequest{
39+
description: fmt.Sprintf("%s -> unlimited", label),
40+
apply: func() error {
41+
current := &syscall.Rlimit{}
42+
if err := syscall.Getrlimit(resource, current); err != nil {
43+
return fmt.Errorf("failed reading %s: %w", label, err)
44+
}
45+
46+
unlimited := ^uint64(0)
47+
desired := &syscall.Rlimit{Cur: unlimited, Max: unlimited}
48+
if current.Cur == desired.Cur && current.Max == desired.Max {
49+
return nil
50+
}
51+
52+
if err := syscall.Setrlimit(resource, desired); err != nil {
53+
return fmt.Errorf("failed setting %s to unlimited: %w", label, err)
54+
}
55+
return nil
56+
},
57+
}
58+
}
59+
60+
// buildTargetRequest nudges a resource toward the requested level while honoring the hard ceiling.
61+
// When raising the hard limit is denied, the fallback keeps the process running with the best available values.
62+
func buildTargetRequest(label string, resource int, target uint64, logger *log.Logger) limitRequest {
63+
return limitRequest{
64+
description: fmt.Sprintf("%s -> %d", label, target),
65+
apply: func() error {
66+
current := &syscall.Rlimit{}
67+
if err := syscall.Getrlimit(resource, current); err != nil {
68+
return fmt.Errorf("failed reading %s: %w", label, err)
69+
}
70+
71+
desired := &syscall.Rlimit{Cur: target, Max: target}
72+
if current.Max > desired.Max {
73+
desired.Max = current.Max
74+
}
75+
if desired.Cur > desired.Max {
76+
desired.Cur = desired.Max
77+
}
78+
79+
if current.Cur >= desired.Cur && current.Max >= desired.Max {
80+
return nil
81+
}
82+
83+
if err := syscall.Setrlimit(resource, desired); err != nil {
84+
logger.Printf("Adjusting %s hit %v; trying best-effort with existing max", label, err)
85+
fallback := &syscall.Rlimit{Cur: desired.Cur, Max: current.Max}
86+
if fallback.Cur > fallback.Max {
87+
fallback.Cur = fallback.Max
88+
}
89+
if setErr := syscall.Setrlimit(resource, fallback); setErr != nil {
90+
return fmt.Errorf("failed setting %s even after fallback: %w", label, setErr)
91+
}
92+
}
93+
return nil
94+
},
95+
}
96+
}

pkg/limits/limits_posix_freebsd.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//go:build freebsd
2+
// +build freebsd
3+
4+
// Package limits includes POSIX-specific limit tuning to mirror xinetd-like defaults on FreeBSD.
5+
// Using a FreeBSD-focused file keeps type handling compatible with the signed Rlimit definitions.
6+
package limits
7+
8+
import (
9+
"fmt"
10+
"log"
11+
"syscall"
12+
)
13+
14+
// collectLimitRequests assembles the desired RLIMIT adjustments for macOS and FreeBSD.
15+
// Keeping the list together documents which resources mirror the xinetd expectations.
16+
func collectLimitRequests(logger *log.Logger) []limitRequest {
17+
desiredOpenFiles := int64(100000)
18+
desiredProcesses := int64(100000)
19+
20+
requests := []limitRequest{
21+
buildInfinityRequest("virtual memory (rlimit_as)", syscall.RLIMIT_AS),
22+
buildInfinityRequest("CPU time (rlimit_cpu)", syscall.RLIMIT_CPU),
23+
buildTargetRequest("open files (rlimit_files)", syscall.RLIMIT_NOFILE, desiredOpenFiles, logger),
24+
}
25+
26+
if procResource, ok := processLimitResource(); ok {
27+
requests = append(requests, buildTargetRequest("process count (rlimit_proc)", procResource, desiredProcesses, logger))
28+
} else {
29+
logger.Printf("Process limit resource is unavailable on this platform; skipping rlimit_proc")
30+
}
31+
32+
return requests
33+
}
34+
35+
// buildInfinityRequest raises a resource to RLIM_INFINITY so workloads are not capped unexpectedly.
36+
// Using RLIM_INFINITY matches the signed fields exposed by the BSD syscall package.
37+
func buildInfinityRequest(label string, resource int) limitRequest {
38+
return limitRequest{
39+
description: fmt.Sprintf("%s -> unlimited", label),
40+
apply: func() error {
41+
current := &syscall.Rlimit{}
42+
if err := syscall.Getrlimit(resource, current); err != nil {
43+
return fmt.Errorf("failed reading %s: %w", label, err)
44+
}
45+
46+
desired := &syscall.Rlimit{Cur: syscall.RLIM_INFINITY, Max: syscall.RLIM_INFINITY}
47+
if current.Cur == desired.Cur && current.Max == desired.Max {
48+
return nil
49+
}
50+
51+
if err := syscall.Setrlimit(resource, desired); err != nil {
52+
return fmt.Errorf("failed setting %s to unlimited: %w", label, err)
53+
}
54+
return nil
55+
},
56+
}
57+
}
58+
59+
// buildTargetRequest nudges a resource toward the requested level while honoring the hard ceiling.
60+
// When raising the hard limit is denied, the fallback keeps the process running with the best available values.
61+
func buildTargetRequest(label string, resource int, target int64, logger *log.Logger) limitRequest {
62+
return limitRequest{
63+
description: fmt.Sprintf("%s -> %d", label, target),
64+
apply: func() error {
65+
current := &syscall.Rlimit{}
66+
if err := syscall.Getrlimit(resource, current); err != nil {
67+
return fmt.Errorf("failed reading %s: %w", label, err)
68+
}
69+
70+
desired := &syscall.Rlimit{Cur: target, Max: target}
71+
if current.Max > desired.Max {
72+
desired.Max = current.Max
73+
}
74+
if desired.Cur > desired.Max {
75+
desired.Cur = desired.Max
76+
}
77+
78+
if current.Cur >= desired.Cur && current.Max >= desired.Max {
79+
return nil
80+
}
81+
82+
if err := syscall.Setrlimit(resource, desired); err != nil {
83+
logger.Printf("Adjusting %s hit %v; trying best-effort with existing max", label, err)
84+
fallback := &syscall.Rlimit{Cur: desired.Cur, Max: current.Max}
85+
if fallback.Cur > fallback.Max {
86+
fallback.Cur = fallback.Max
87+
}
88+
if setErr := syscall.Setrlimit(resource, fallback); setErr != nil {
89+
return fmt.Errorf("failed setting %s even after fallback: %w", label, setErr)
90+
}
91+
}
92+
return nil
93+
},
94+
}
95+
}

pkg/limits/limits_posix_openbsd.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
// collectLimitRequests assembles the desired RLIMIT adjustments for OpenBSD.
1515
// RLIMIT_DATA stands in for address space limits because RLIMIT_AS is unavailable on this platform.
1616
func collectLimitRequests(logger *log.Logger) []limitRequest {
17-
desiredOpenFiles := int64(100000)
18-
desiredProcesses := int64(100000)
17+
desiredOpenFiles := uint64(100000)
18+
desiredProcesses := uint64(100000)
1919

2020
requests := []limitRequest{
2121
buildInfinityRequest("data segment (rlimit_data)", syscall.RLIMIT_DATA),
@@ -33,7 +33,7 @@ func collectLimitRequests(logger *log.Logger) []limitRequest {
3333
}
3434

3535
// buildInfinityRequest raises a resource to RLIM_INFINITY so the proxy is not capped prematurely.
36-
// Using the constant directly matches the int64 fields exposed by the OpenBSD syscall package.
36+
// Using the computed infinity mirrors the unsigned fields exposed by the OpenBSD syscall package.
3737
func buildInfinityRequest(label string, resource int) limitRequest {
3838
return limitRequest{
3939
description: fmt.Sprintf("%s -> unlimited", label),
@@ -43,7 +43,8 @@ func buildInfinityRequest(label string, resource int) limitRequest {
4343
return fmt.Errorf("failed reading %s: %w", label, err)
4444
}
4545

46-
desired := &syscall.Rlimit{Cur: syscall.RLIM_INFINITY, Max: syscall.RLIM_INFINITY}
46+
unlimited := ^uint64(0)
47+
desired := &syscall.Rlimit{Cur: unlimited, Max: unlimited}
4748
if current.Cur == desired.Cur && current.Max == desired.Max {
4849
return nil
4950
}
@@ -58,7 +59,7 @@ func buildInfinityRequest(label string, resource int) limitRequest {
5859

5960
// buildTargetRequest nudges a resource toward the requested target and keeps the hard limit unchanged when required.
6061
// The fallback path maintains availability even if the kernel refuses to raise the maximum.
61-
func buildTargetRequest(label string, resource int, target int64, logger *log.Logger) limitRequest {
62+
func buildTargetRequest(label string, resource int, target uint64, logger *log.Logger) limitRequest {
6263
return limitRequest{
6364
description: fmt.Sprintf("%s -> %d", label, target),
6465
apply: func() error {

0 commit comments

Comments
 (0)