)
var (
+ // startWatcherMu guards the `watched` map.
startWatcherMu sync.Mutex
- gWatcher *fsnotify.Watcher
- logger log.Logger
+
+ // handlerMu guards watchHandlers
+ handlerMu sync.RWMutex
+ gWatcher *fsnotify.Watcher
+ logger log.Logger
)
// Map of original path => notifyRule.
// map of {watched symlink path => rule name}, used to keep track of which symlinks we added
// watchers on in order to detect changes to watched files
-var symlinkPropagationMap map[string]*hashset.HashSet[string]
+var (
+ symlinkPropagationMap map[string]*hashset.HashSet[string]
+ symlinkMu sync.RWMutex
+)
func startWatcher() error {
startWatcherMu.Lock()
}
func watcherLoop() {
- defer (func() {
+ defer func() {
gWatcher = nil
- })()
+ }()
for {
select {
delete(watched, event.Name)
startWatcherMu.Unlock()
}
+
+ handlerMu.RLock()
if _, ok := watchHandlers[event.Name]; ok {
// Event occurred on the original path.
+ handlerMu.RUnlock()
handleEventForPath(event, event.Name)
return
- } else if children, ok := symlinkPropagationMap[event.Name]; ok {
+ }
+ handlerMu.RUnlock()
+
+ symlinkMu.RLock()
+ if children, ok := symlinkPropagationMap[event.Name]; ok {
+ symlinkMu.RUnlock()
// Event occurred on the underlying file (recursively-resolved absolute path) of a watched
// path
for _, child := range children.AsSortedSlice() {
handleEventForPath(event, child)
}
return
- } else if links := symlinkPropagationChildren(event.Name); len(links) > 0 {
+ }
+ symlinkMu.RUnlock()
+
+ if links := symlinkPropagationChildren(event.Name); len(links) > 0 {
// Event occurred on a symlink in the directory chain of the watched path or a link target
// between the original file and the underlying absolute path. Practical example:
//
// In these cases we pick up the event on `/config/..data` and fire events for any original
// paths
for _, link := range links {
- for _, ruleName := range symlinkPropagationMap[link].AsSlice() {
+ symlinkMu.RLock()
+ children := symlinkPropagationMap[link]
+ symlinkMu.RUnlock()
+ for _, ruleName := range children.AsSlice() {
+ handlerMu.RLock()
if _, ok := watchHandlers[ruleName]; ok {
+ handlerMu.RUnlock()
logger.Debugf("propagating %v event on %s to origin %s",
event.Op, link, ruleName)
handleEventForPath(event, ruleName)
} else {
+ handlerMu.RUnlock()
logger.Errorf("symlinkPropagationMap target without corresponding rule: %s", ruleName)
}
}
}
// Don't issue this warning if we received an event for something being created in a directory
// we're watching because of files under it
+ startWatcherMu.Lock()
+ defer startWatcherMu.Unlock()
if _, ok := watched[path.Dir(event.Name)]; ok {
if event.Has(fsnotify.Create) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
return
// multiple watched files is a symlink that gets repointed. For these cases, handleEventForPath is
// called multiple times from a single invocation of handleEvent.
func handleEventForPath(event fsnotify.Event, ruleName string) {
+ handlerMu.RLock()
rule, ok := watchHandlers[ruleName]
+ handlerMu.RUnlock()
if !ok {
logger.Errorf("handleEventForPath called for file without rule: %s: %+v", ruleName, event)
return
if realPath == ruleName {
return
}
+ symlinkMu.Lock()
+ defer symlinkMu.Unlock()
+
if _, ok := symlinkPropagationMap[realPath]; !ok {
symlinkPropagationMap[realPath] = hashset.NewHashSet[string]()
}
}
func scanSymlinks(filePath string) error {
+ handlerMu.RLock()
rule := watchHandlers[filePath]
+ handlerMu.RUnlock()
rule.upstreams.Empty()
target := filePath
for {
}
logger.Debugf("watchHandlers:")
+ handlerMu.RLock()
if len(watchHandlers) == 0 {
logger.Debugf(" <NONE>")
}
for filePath, rule := range watchHandlers {
logger.Debugf(" %s: upstreams=%+v, %+v", filePath, rule.upstreams.AsSortedSlice(), rule.callbacks)
}
+ handlerMu.RUnlock()
logger.Debugf("fsnotify ref counts:")
if len(watched) == 0 {
return err
}
+ handlerMu.Lock()
if _, ok := watchHandlers[filePath]; !ok {
watchHandlers[filePath] = ¬ifyRule{
upstreams: hashset.NewHashSet[string](),
}
}
rule := watchHandlers[filePath]
-
rule.callbacks = append(watchHandlers[filePath].callbacks, callback)
+ handlerMu.Unlock()
scanSymlinks(filePath)