diff --git a/internal/lsp/watcher/ulimit_bsd.go b/internal/lsp/watcher/ulimit_bsd.go new file mode 100644 index 0000000000000000000000000000000000000000..816e82adee5e57341b7e392e117b245a7ca4a0dc --- /dev/null +++ b/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 +} diff --git a/internal/lsp/watcher/ulimit_darwin.go b/internal/lsp/watcher/ulimit_darwin.go new file mode 100644 index 0000000000000000000000000000000000000000..3208222a137641d0b29c530ee505c73f64edf1d5 --- /dev/null +++ b/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 +} diff --git a/internal/lsp/watcher/ulimit_fallback.go b/internal/lsp/watcher/ulimit_fallback.go new file mode 100644 index 0000000000000000000000000000000000000000..118554f25a34aa5921b1773c72d87dc3975324a7 --- /dev/null +++ b/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 +} diff --git a/internal/lsp/watcher/ulimit_linux.go b/internal/lsp/watcher/ulimit_linux.go new file mode 100644 index 0000000000000000000000000000000000000000..298fcad96710eb106ee607ac823962450f892bf3 --- /dev/null +++ b/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 +} diff --git a/internal/lsp/watcher/ulimit_windows.go b/internal/lsp/watcher/ulimit_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..14afbabeea1ce4818bb59a3fc8c5e2ee1fa8432a --- /dev/null +++ b/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 +} diff --git a/internal/lsp/watcher/watcher.go b/internal/lsp/watcher/watcher.go index 75d6a9229b9991945d0e61e21b16c7c3abc2eefe..976bbb291b08b84af578b7e05e2d568cd2ad5d04 100644 --- a/internal/lsp/watcher/watcher.go +++ b/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{