agones.dev/agones@v1.53.0/pkg/util/fswatch/fswatch.go (about) 1 // Copyright 2022 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package fswatch provies Watch(), a utility function to watch a filesystem path. 16 package fswatch 17 18 import ( 19 "time" 20 21 "github.com/sirupsen/logrus" 22 "gopkg.in/fsnotify.v1" 23 ) 24 25 // Watch watches the filesystem path `path`. When anything changes, changes are 26 // batched for the period `batchFor`, then `processEvent` is called. 27 // 28 // Returns a cancel() function to terminate the watch. 29 func Watch(logger *logrus.Entry, path string, batchFor time.Duration, processEvent func()) (func(), error) { 30 logger = logger.WithField("path", path) 31 watcher, err := fsnotify.NewWatcher() 32 if err != nil { 33 return nil, err 34 } 35 cancelChan := make(chan struct{}) 36 cancel := func() { 37 close(cancelChan) 38 _ = watcher.Close() 39 } 40 if err := watcher.Add(path); err != nil { 41 cancel() 42 return nil, err 43 } 44 45 go batchWatch(batchFor, watcher.Events, watcher.Errors, cancelChan, processEvent, func(error) { 46 logger.WithError(err).Errorf("error watching path") 47 }) 48 return cancel, nil 49 } 50 51 // batchWatch: watch for events; when an event occurs, keep draining events for duration `batchFor`, then call processEvent(). 52 // Intended for batching of rapid-fire events where we want to process the batch once, like filesystem update notifications. 53 func batchWatch(batchFor time.Duration, events chan fsnotify.Event, errors chan error, cancelChan chan struct{}, processEvent func(), onError func(error)) { 54 // Pattern shamelessly stolen from https://blog.gopheracademy.com/advent-2013/day-24-channel-buffering-patterns/ 55 timer := time.NewTimer(0) 56 var timerCh <-chan time.Time 57 58 for { 59 select { 60 // start a timer when an event occurs, otherwise ignore event 61 case <-events: 62 if timerCh == nil { 63 timer.Reset(batchFor) 64 timerCh = timer.C 65 } 66 67 // on timer, run the batch; nil channels are silently ignored 68 case <-timerCh: 69 processEvent() 70 timerCh = nil 71 72 // handle errors 73 case err := <-errors: 74 onError(err) 75 76 // on cancel, abort 77 case <-cancelChan: 78 return 79 } 80 } 81 }