Detailed changes
@@ -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
+}
@@ -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
+}
@@ -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
+}
@@ -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
+}
@@ -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
+}
@@ -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{