automatically increase the ulimit to 80% of maximum for crush

Tai Groot created

Change summary

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 ++++
6 files changed, 128 insertions(+)

Detailed changes

internal/lsp/watcher/ulimit_bsd.go 🔗

@@ -0,0 +1,25 @@
+//go:build freebsd || openbsd || netbsd || dragonfly
+
+package watcher
+
+import "syscall"
+
+func Ulimit() (uint64, error) {
+	var currentLimit uint64 = 0
+	var rLimit syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return 0, err
+	}
+	currentLimit = uint64(rLimit.Cur)
+	rLimit.Cur = rLimit.Max / 10 * 8
+	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	return uint64(rLimit.Cur), nil
+}

internal/lsp/watcher/ulimit_darwin.go 🔗

@@ -0,0 +1,25 @@
+//go:build darwin
+
+package watcher
+
+import "syscall"
+
+func Ulimit() (uint64, error) {
+	var currentLimit uint64 = 0
+	var rLimit syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return 0, err
+	}
+	currentLimit = rLimit.Cur
+	rLimit.Cur = rLimit.Max / 10 * 8
+	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	return rLimit.Cur, nil
+}

internal/lsp/watcher/ulimit_fallback.go 🔗

@@ -0,0 +1,8 @@
+//go:build !linux && !darwin && !freebsd && !openbsd && !netbsd && !dragonfly && !windows
+
+package watcher
+
+func Ulimit() (uint64, error) {
+	// Fallback for exotic systems - return a reasonable default
+	return 2048, nil
+}

internal/lsp/watcher/ulimit_linux.go 🔗

@@ -0,0 +1,25 @@
+//go:build linux
+
+package watcher
+
+import "syscall"
+
+func Ulimit() (uint64, error) {
+	var currentLimit uint64 = 0
+	var rLimit syscall.Rlimit
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return 0, err
+	}
+	currentLimit = rLimit.Cur
+	rLimit.Cur = rLimit.Max / 10 * 8
+	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+	if err != nil {
+		return currentLimit, err
+	}
+	return rLimit.Cur, nil
+}

internal/lsp/watcher/ulimit_windows.go 🔗

@@ -0,0 +1,38 @@
+//go:build windows
+
+package watcher
+
+import (
+	"syscall"
+	"unsafe"
+
+	"golang.org/x/sys/windows"
+)
+
+var (
+	kernel32                  = windows.NewLazyDLL("kernel32.dll")
+	procGetProcessHandleCount = kernel32.NewProc("GetProcessHandleCount")
+)
+
+func Ulimit() (uint64, error) {
+	// Windows doesn't have the same file descriptor limits as Unix systems
+	// Instead, we can get the current handle count for monitoring purposes
+	currentProcess := windows.CurrentProcess()
+
+	var handleCount uint32
+	ret, _, err := procGetProcessHandleCount.Call(
+		uintptr(currentProcess),
+		uintptr(unsafe.Pointer(&handleCount)),
+	)
+
+	if ret == 0 {
+		// If the call failed, return a reasonable default
+		if err != syscall.Errno(0) {
+			return 2048, nil
+		}
+	}
+
+	// Windows typically allows much higher handle counts than Unix file descriptors
+	// Return the current count, which serves as a baseline for monitoring
+	return uint64(handleCount), nil
+}

internal/lsp/watcher/watcher.go 🔗

@@ -33,6 +33,13 @@ type WorkspaceWatcher struct {
 	registrationMu sync.RWMutex
 }
 
+func init() {
+	// Ensure the watcher is initialized with a reasonable file limit
+	if _, err := Ulimit(); err != nil {
+		slog.Error("Error setting file limit", "error", err)
+	}
+}
+
 // NewWorkspaceWatcher creates a new workspace watcher
 func NewWorkspaceWatcher(name string, client *lsp.Client) *WorkspaceWatcher {
 	return &WorkspaceWatcher{