Skip to content

Commit 39ebb72

Browse files
committed
enhance(cache): handle symlinks in file event handler
1 parent e7e15bb commit 39ebb72

File tree

7 files changed

+117
-29
lines changed

7 files changed

+117
-29
lines changed

api/analytic/analytic.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func Analytic(c *gin.Context) {
9494

9595
select {
9696
case <-kernel.Context.Done():
97+
logger.Debug("Analytic: Context cancelled, closing WebSocket")
9798
return
9899
case <-time.After(1 * time.Second):
99100
}

api/analytic/nodes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func GetNodeStat(c *gin.Context) {
8686

8787
select {
8888
case <-kernel.Context.Done():
89+
logger.Debug("GetNodeStat: Context cancelled, closing WebSocket")
8990
return
9091
case <-time.After(10 * time.Second):
9192
}
@@ -119,6 +120,7 @@ func GetNodesAnalytic(c *gin.Context) {
119120

120121
select {
121122
case <-kernel.Context.Done():
123+
logger.Debug("GetNodesAnalytic: Context cancelled, closing WebSocket")
122124
return
123125
case <-time.After(10 * time.Second):
124126
}

api/event/websocket.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ func (c *Client) writePump() {
179179
return
180180

181181
case <-kernel.Context.Done():
182+
logger.Debug("EventBus: Context cancelled, closing WebSocket")
182183
return
183184
}
184185
}

api/nginx/websocket.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func (h *NginxPerformanceHub) run() {
8080
h.broadcastPerformanceData()
8181

8282
case <-kernel.Context.Done():
83+
logger.Debug("NginxPerformanceHub: Context cancelled, closing WebSocket")
8384
// Shutdown all clients
8485
h.mutex.Lock()
8586
for client := range h.clients {
@@ -200,6 +201,7 @@ func (c *NginxPerformanceClient) writePump() {
200201
return
201202

202203
case <-kernel.Context.Done():
204+
logger.Debug("NginxPerformanceClient: Context cancelled, closing WebSocket")
203205
return
204206
}
205207
}

api/upstream/upstream.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/0xJacky/Nginx-UI/internal/helper"
10+
"github.com/0xJacky/Nginx-UI/internal/kernel"
1011
"github.com/0xJacky/Nginx-UI/internal/upstream"
1112
"github.com/gin-gonic/gin"
1213
"github.com/gorilla/websocket"
@@ -108,6 +109,9 @@ func AvailabilityWebSocket(c *gin.Context) {
108109
return
109110
}
110111
}
112+
case <-kernel.Context.Done():
113+
logger.Debug("AvailabilityWebSocket: Context cancelled, closing WebSocket")
114+
return
111115
}
112116
}
113117
}
@@ -143,10 +147,3 @@ func unregisterWebSocketConnection() {
143147
}
144148
logger.Debug("WebSocket connection unregistered, remaining connections:", wsConnections)
145149
}
146-
147-
// HasActiveWebSocketConnections returns true if there are active WebSocket connections
148-
func HasActiveWebSocketConnections() bool {
149-
wsConnectionMutex.Lock()
150-
defer wsConnectionMutex.Unlock()
151-
return wsConnections > 0
152-
}

internal/cache/index.go

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cache
22

33
import (
44
"context"
5+
"fmt"
56
"io/fs"
67
"os"
78
"path/filepath"
@@ -132,11 +133,24 @@ func (s *Scanner) watchAllDirectories() error {
132133
return filepath.SkipDir
133134
}
134135

135-
if err := s.watcher.Add(path); err != nil {
136-
logger.Error("Failed to watch directory:", path, err)
136+
// Resolve symlinks to get the actual directory path to watch
137+
actualPath := path
138+
if d.Type()&os.ModeSymlink != 0 {
139+
// This is a symlink, resolve it to get the target path
140+
if resolvedPath, err := filepath.EvalSymlinks(path); err == nil {
141+
actualPath = resolvedPath
142+
logger.Debug("Resolved symlink for watching:", path, "->", actualPath)
143+
} else {
144+
logger.Debug("Failed to resolve symlink, skipping:", path, err)
145+
return filepath.SkipDir
146+
}
147+
}
148+
149+
if err := s.watcher.Add(actualPath); err != nil {
150+
logger.Error("Failed to watch directory:", actualPath, err)
137151
return err
138152
}
139-
// logger.Debug("Watching directory:", path)
153+
logger.Debug("Watching directory:", actualPath)
140154
}
141155
return nil
142156
})
@@ -216,12 +230,28 @@ func (s *Scanner) handleFileEvent(event fsnotify.Event) {
216230
return
217231
}
218232

219-
fi, err := os.Stat(event.Name)
233+
// Use Lstat to get symlink info without following it
234+
fi, err := os.Lstat(event.Name)
220235
if err != nil {
221236
return
222237
}
223238

224-
if fi.IsDir() {
239+
// If it's a symlink, we need to check what it points to
240+
var targetIsDir bool
241+
if fi.Mode()&os.ModeSymlink != 0 {
242+
// For symlinks, check the target
243+
targetFi, err := os.Stat(event.Name)
244+
if err != nil {
245+
logger.Debug("Symlink target not accessible:", event.Name, err)
246+
return
247+
}
248+
targetIsDir = targetFi.IsDir()
249+
logger.Debug("Symlink changed:", event.Name, "-> target is dir:", targetIsDir)
250+
} else {
251+
targetIsDir = fi.IsDir()
252+
}
253+
254+
if targetIsDir {
225255
logger.Debug("Directory changed:", event.Name)
226256
} else {
227257
logger.Debug("File changed:", event.Name)
@@ -252,10 +282,24 @@ func (s *Scanner) scanSingleFile(filePath string) error {
252282
return nil
253283
}
254284

255-
// Skip symlinks to avoid potential issues
285+
// Handle symlinks carefully
256286
if fileInfo.Mode()&os.ModeSymlink != 0 {
257-
logger.Debugf("Skipping symlink: %s", filePath)
258-
return nil
287+
// Check what the symlink points to
288+
targetInfo, err := os.Stat(filePath)
289+
if err != nil {
290+
logger.Debugf("Skipping symlink with inaccessible target: %s (%v)", filePath, err)
291+
return nil
292+
}
293+
294+
// Skip symlinks to directories
295+
if targetInfo.IsDir() {
296+
logger.Debugf("Skipping symlink to directory: %s", filePath)
297+
return nil
298+
}
299+
300+
// Process symlinks to files, but use the target's info for size check
301+
fileInfo = targetInfo
302+
logger.Debugf("Processing symlink to file: %s", filePath)
259303
}
260304

261305
// Skip non-regular files (devices, pipes, sockets, etc.)
@@ -326,7 +370,22 @@ func (s *Scanner) ScanAllConfigs() error {
326370
return filepath.SkipDir
327371
}
328372

329-
// Only process regular files
373+
// Handle symlinks to directories specially
374+
if d.Type()&os.ModeSymlink != 0 {
375+
if targetInfo, err := os.Stat(path); err == nil && targetInfo.IsDir() {
376+
// This is a symlink to a directory, we should traverse its contents
377+
// but not process the symlink itself as a file
378+
logger.Debug("Found symlink to directory, will traverse contents:", path)
379+
380+
// Manually scan the symlink target directory since WalkDir doesn't follow symlinks
381+
if err := s.scanSymlinkDirectory(path); err != nil {
382+
logger.Error("Failed to scan symlink directory:", path, err)
383+
}
384+
return nil
385+
}
386+
}
387+
388+
// Only process regular files (not directories, not symlinks to directories)
330389
if !d.IsDir() {
331390
if err := s.scanSingleFile(path); err != nil {
332391
logger.Error("Failed to scan config:", path, err)
@@ -337,6 +396,45 @@ func (s *Scanner) ScanAllConfigs() error {
337396
})
338397
}
339398

399+
// scanSymlinkDirectory recursively scans a symlink directory and its contents
400+
func (s *Scanner) scanSymlinkDirectory(symlinkPath string) error {
401+
// Resolve the symlink to get the actual target path
402+
targetPath, err := filepath.EvalSymlinks(symlinkPath)
403+
if err != nil {
404+
return fmt.Errorf("failed to resolve symlink %s: %w", symlinkPath, err)
405+
}
406+
407+
logger.Debug("Scanning symlink directory contents:", symlinkPath, "->", targetPath)
408+
409+
// Use WalkDir on the resolved target path
410+
return filepath.WalkDir(targetPath, func(path string, d fs.DirEntry, err error) error {
411+
if err != nil {
412+
return err
413+
}
414+
415+
// Skip excluded directories
416+
if d.IsDir() && shouldSkipPath(path) {
417+
return filepath.SkipDir
418+
}
419+
420+
// Only process regular files (not directories, not symlinks to directories)
421+
if !d.IsDir() {
422+
// Handle symlinks to directories (skip them)
423+
if d.Type()&os.ModeSymlink != 0 {
424+
if targetInfo, err := os.Stat(path); err == nil && targetInfo.IsDir() {
425+
logger.Debug("Skipping symlink to directory in symlink scan:", path)
426+
return nil
427+
}
428+
}
429+
430+
if err := s.scanSingleFile(path); err != nil {
431+
logger.Error("Failed to scan config in symlink directory:", path, err)
432+
}
433+
}
434+
return nil
435+
})
436+
}
437+
340438
// Shutdown cleans up scanner resources
341439
func (s *Scanner) Shutdown() {
342440
if s.watcher != nil {

internal/cron/upstream_availability.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cron
33
import (
44
"time"
55

6-
apiUpstream "github.com/0xJacky/Nginx-UI/api/upstream"
76
"github.com/0xJacky/Nginx-UI/internal/upstream"
87
"github.com/go-co-op/gocron/v2"
98
"github.com/uozi-tech/cosy/logger"
@@ -41,13 +40,6 @@ func executeUpstreamAvailabilityTest() {
4140
return
4241
}
4342

44-
// Check if we should skip this test due to active WebSocket connections
45-
// (WebSocket connections trigger more frequent checks)
46-
if hasActiveWebSocketConnections() {
47-
logger.Debug("Skipping scheduled test due to active WebSocket connections")
48-
return
49-
}
50-
5143
start := time.Now()
5244
logger.Debug("Starting scheduled upstream availability test for", targetCount, "targets")
5345

@@ -57,11 +49,6 @@ func executeUpstreamAvailabilityTest() {
5749
logger.Debug("Upstream availability test completed in", duration)
5850
}
5951

60-
// hasActiveWebSocketConnections checks if there are active WebSocket connections
61-
func hasActiveWebSocketConnections() bool {
62-
return apiUpstream.HasActiveWebSocketConnections()
63-
}
64-
6552
// RestartUpstreamAvailabilityJob restarts the upstream availability job
6653
func RestartUpstreamAvailabilityJob() error {
6754
logger.Info("Restarting upstream availability job...")

0 commit comments

Comments
 (0)