From 87d6aae34080a17cbf4e705afe6da657f80c8e42 Mon Sep 17 00:00:00 2001 From: johnlanni Date: Sun, 1 Feb 2026 23:35:46 +0800 Subject: [PATCH] fix: revert to per-resource watcher + increase FD limit in main.go The shared watcher approach has fundamental issues: - Each fileREST instance starts a goroutine reading from the same Events channel - This causes event distribution conflicts between resources - Multiple resources adding the same directory causes issues Solution: 1. Revert to each resource having its own watcher 2. Increase file descriptor limit to 65535 in main.go init() 3. Add better error messages when watcher creation fails This ensures proper event isolation while providing enough FD headroom. --- src/apiserver/main.go | 30 ++++++++++++++++++++++++ src/apiserver/pkg/apiserver/apiserver.go | 12 +--------- src/apiserver/pkg/registry/file_rest.go | 12 ++++++---- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/apiserver/main.go b/src/apiserver/main.go index 5d3d2ca..2be3f8a 100644 --- a/src/apiserver/main.go +++ b/src/apiserver/main.go @@ -17,13 +17,43 @@ limitations under the License. package main import ( + "fmt" "os" + "syscall" "github.com/alibaba/higress/api-server/pkg/cmd/server" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/component-base/cli" ) +func init() { + // Increase file descriptor limit early to avoid "too many open files" errors + var rLimit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to get rlimit: %v\n", err) + return + } + + fmt.Printf("Current file descriptor limit: soft=%d hard=%d\n", rLimit.Cur, rLimit.Max) + + // Try to set soft limit to 65535 + targetLimit := uint64(65535) + if rLimit.Cur < targetLimit { + rLimit.Cur = targetLimit + // If hard limit is less than target, try to increase it (may require root) + if rLimit.Max < targetLimit { + rLimit.Max = targetLimit + } + + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to set rlimit to %d: %v\n", targetLimit, err) + return + } + + fmt.Printf("Updated file descriptor limit: soft=%d hard=%d\n", rLimit.Cur, rLimit.Max) + } +} + func main() { stopCh := genericapiserver.SetupSignalHandler() options := server.NewHigressServerOptions(os.Stdout, os.Stderr) diff --git a/src/apiserver/pkg/apiserver/apiserver.go b/src/apiserver/pkg/apiserver/apiserver.go index 77b8e44..24557e8 100644 --- a/src/apiserver/pkg/apiserver/apiserver.go +++ b/src/apiserver/pkg/apiserver/apiserver.go @@ -19,7 +19,6 @@ package apiserver import ( "fmt" - "github.com/fsnotify/fsnotify" istiov1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" admregv1 "k8s.io/api/admissionregistration/v1" authzv1 "k8s.io/api/authorization/v1" @@ -170,15 +169,6 @@ func (c completedConfig) New() (*HigressServer, error) { } } - // Create shared watcher for file storage mode to avoid "too many open files" - var sharedWatcher *fsnotify.Watcher - if storageMode == options.Storage_File { - sharedWatcher, err = fsnotify.NewWatcher() - if err != nil { - return nil, fmt.Errorf("failed to create shared file watcher: %v", err) - } - } - storageCreateFunc := func( groupResource schema.GroupResource, runtimeCodec runtime.Codec, @@ -195,7 +185,7 @@ func (c completedConfig) New() (*HigressServer, error) { switch storageMode { case options.Storage_File: runtimeCodec = codec.NewFlatAwareCodec(groupResource, runtimeCodec) - return registry.NewFileREST(groupResource, runtimeCodec, storageOptions.FileOptions.RootDir, extension, isNamespaced, singularName, newFunc, newListFunc, attrFunc, sharedWatcher) + return registry.NewFileREST(groupResource, runtimeCodec, storageOptions.FileOptions.RootDir, extension, isNamespaced, singularName, newFunc, newListFunc, attrFunc) case options.Storage_Nacos: var encryptionKey []byte = nil if sensitive { diff --git a/src/apiserver/pkg/registry/file_rest.go b/src/apiserver/pkg/registry/file_rest.go index 35b1e23..b3cd81d 100644 --- a/src/apiserver/pkg/registry/file_rest.go +++ b/src/apiserver/pkg/registry/file_rest.go @@ -54,7 +54,6 @@ func NewFileREST( newFunc func() runtime.Object, newListFunc func() runtime.Object, attrFunc storage.AttrFunc, - sharedWatcher *fsnotify.Watcher, ) (REST, error) { if attrFunc == nil { if isNamespaced { @@ -63,6 +62,10 @@ func NewFileREST( attrFunc = storage.DefaultClusterScopedAttr } } + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, fmt.Errorf("failed to create file watcher for %s: %v", groupResource.Resource, err) + } // file REST f := &fileREST{ TableConvertor: rest.NewDefaultTableConvertor(groupResource), @@ -75,7 +78,7 @@ func NewFileREST( newFunc: newFunc, newListFunc: newListFunc, attrFunc: attrFunc, - dirWatcher: sharedWatcher, + dirWatcher: watcher, fileWatchers: make(map[string]*fileWatch, 10), } if err := f.startDirWatcher(); err != nil { @@ -111,8 +114,9 @@ func (f *fileREST) GetSingularName() string { } func (f *fileREST) Destroy() { - // Don't close the watcher as it's shared across all fileREST instances - // The watcher will be closed when the server shuts down + if f.dirWatcher != nil { + _ = f.dirWatcher.Close() + } } func (f *fileREST) startDirWatcher() error {