on main help sign in
Merge pull request #258 from charmbracelet/bugfix/ulimit-increase

automatically increase the ulimit to 80% of maximum for crush PID

commitc570ded3e79bd74b5e0f94c0aa8a7fc49c6dec06 authorKujtim Hoxha <kujtimii.h@gmail.com> dateTue 22 Jul 2025, 12:44 (1 year ago) parent2c0d70ca9f136d
6 files changed+128
internal/lsp/watcher/ulimit_bsd.go+25 internal/lsp/watcher/ulimit_darwin.go+25 internal/lsp/watcher/ulimit_fallback.go+8 internal/lsp/watcher/ulimit_linux.go+25 internal/lsp/watcher/ulimit_windows.go+38 internal/lsp/watcher/watcher.go+7
internal/lsp/watcher/ulimit_bsd.gonew file+25
@@ -0,0 +1,25 @@
1+//go:build freebsd || openbsd || netbsd || dragonfly
2+
3+package watcher
4+
5+import "syscall"
6+
7+func Ulimit() (uint64, error) {
8+ var currentLimit uint64 = 0
9+ var rLimit syscall.Rlimit
10+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
11+ if err != nil {
12+ return 0, err
13+ }
14+ currentLimit = uint64(rLimit.Cur)
15+ rLimit.Cur = rLimit.Max / 10 * 8
16+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
17+ if err != nil {
18+ return currentLimit, err
19+ }
20+ err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
21+ if err != nil {
22+ return currentLimit, err
23+ }
24+ return uint64(rLimit.Cur), nil
25+}
internal/lsp/watcher/ulimit_darwin.gonew file+25
@@ -0,0 +1,25 @@
1+//go:build darwin
2+
3+package watcher
4+
5+import "syscall"
6+
7+func Ulimit() (uint64, error) {
8+ var currentLimit uint64 = 0
9+ var rLimit syscall.Rlimit
10+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
11+ if err != nil {
12+ return 0, err
13+ }
14+ currentLimit = rLimit.Cur
15+ rLimit.Cur = rLimit.Max / 10 * 8
16+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
17+ if err != nil {
18+ return currentLimit, err
19+ }
20+ err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
21+ if err != nil {
22+ return currentLimit, err
23+ }
24+ return rLimit.Cur, nil
25+}
internal/lsp/watcher/ulimit_fallback.gonew file+8
@@ -0,0 +1,8 @@
1+//go:build !linux && !darwin && !freebsd && !openbsd && !netbsd && !dragonfly && !windows
2+
3+package watcher
4+
5+func Ulimit() (uint64, error) {
6+ // Fallback for exotic systems - return a reasonable default
7+ return 2048, nil
8+}
internal/lsp/watcher/ulimit_linux.gonew file+25
@@ -0,0 +1,25 @@
1+//go:build linux
2+
3+package watcher
4+
5+import "syscall"
6+
7+func Ulimit() (uint64, error) {
8+ var currentLimit uint64 = 0
9+ var rLimit syscall.Rlimit
10+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
11+ if err != nil {
12+ return 0, err
13+ }
14+ currentLimit = rLimit.Cur
15+ rLimit.Cur = rLimit.Max / 10 * 8
16+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
17+ if err != nil {
18+ return currentLimit, err
19+ }
20+ err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
21+ if err != nil {
22+ return currentLimit, err
23+ }
24+ return rLimit.Cur, nil
25+}
internal/lsp/watcher/ulimit_windows.gonew file+38
@@ -0,0 +1,38 @@
1+//go:build windows
2+
3+package watcher
4+
5+import (
6+ "syscall"
7+ "unsafe"
8+
9+ "golang.org/x/sys/windows"
10+)
11+
12+var (
13+ kernel32 = windows.NewLazyDLL("kernel32.dll")
14+ procGetProcessHandleCount = kernel32.NewProc("GetProcessHandleCount")
15+)
16+
17+func Ulimit() (uint64, error) {
18+ // Windows doesn't have the same file descriptor limits as Unix systems
19+ // Instead, we can get the current handle count for monitoring purposes
20+ currentProcess := windows.CurrentProcess()
21+
22+ var handleCount uint32
23+ ret, _, err := procGetProcessHandleCount.Call(
24+ uintptr(currentProcess),
25+ uintptr(unsafe.Pointer(&handleCount)),
26+ )
27+
28+ if ret == 0 {
29+ // If the call failed, return a reasonable default
30+ if err != syscall.Errno(0) {
31+ return 2048, nil
32+ }
33+ }
34+
35+ // Windows typically allows much higher handle counts than Unix file descriptors
36+ // Return the current count, which serves as a baseline for monitoring
37+ return uint64(handleCount), nil
38+}
internal/lsp/watcher/watcher.go+7
@@ -33,6 +33,13 @@ type WorkspaceWatcher struct {
3333 registrationMu sync.RWMutex
3434}
3535
36+func init() {
37+ // Ensure the watcher is initialized with a reasonable file limit
38+ if _, err := Ulimit(); err != nil {
39+ slog.Error("Error setting file limit", "error", err)
40+ }
41+}
42+
3643// NewWorkspaceWatcher creates a new workspace watcher
3744func NewWorkspaceWatcher(name string, client *lsp.Client) *WorkspaceWatcher {
3845 return &WorkspaceWatcher{