X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/7e85063ebffe3a6d990fb0b2eac62b1906660a21..bc3637c90f8e4e3d1bdc61546c3d7fc53da135f6:/lib/controller/localdb/conn.go diff --git a/lib/controller/localdb/conn.go b/lib/controller/localdb/conn.go index edbbcb09cd..323e660c6f 100644 --- a/lib/controller/localdb/conn.go +++ b/lib/controller/localdb/conn.go @@ -11,23 +11,25 @@ import ( "net/http" "os" "strings" + "time" "git.arvados.org/arvados.git/lib/controller/railsproxy" "git.arvados.org/arvados.git/lib/controller/rpc" "git.arvados.org/arvados.git/sdk/go/arvados" "git.arvados.org/arvados.git/sdk/go/ctxlog" "git.arvados.org/arvados.git/sdk/go/httpserver" - "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" ) type railsProxy = rpc.Conn type Conn struct { - cluster *arvados.Cluster - *railsProxy // handles API methods that aren't defined on Conn itself - vocabularyCache *arvados.Vocabulary - reloadVocabulary bool + cluster *arvados.Cluster + *railsProxy // handles API methods that aren't defined on Conn itself + vocabularyCache *arvados.Vocabulary + vocabularyFileModTime time.Time + lastVocabularyRefreshCheck time.Time + lastVocabularyError error loginController } @@ -69,43 +71,35 @@ func (conn *Conn) checkProperties(ctx context.Context, properties interface{}) e return nil } -func watchVocabulary(logger logrus.FieldLogger, vocPath string, fn func()) { - watcher, err := fsnotify.NewWatcher() - if err != nil { - logger.WithError(err).Error("vocabulary fsnotify setup failed") - return +func (conn *Conn) maybeRefreshVocabularyCache(logger logrus.FieldLogger) error { + if conn.lastVocabularyRefreshCheck.Add(time.Second).After(time.Now()) { + // Throttle the access to disk to at most once per second. + return nil } - defer watcher.Close() - - err = watcher.Add(vocPath) + conn.lastVocabularyRefreshCheck = time.Now() + fi, err := os.Stat(conn.cluster.API.VocabularyPath) if err != nil { - logger.WithError(err).Error("vocabulary file watcher failed") - return + err = fmt.Errorf("couldn't stat vocabulary file %q: %v", conn.cluster.API.VocabularyPath, err) + conn.lastVocabularyError = err + return err } - - for { - select { - case err, ok := <-watcher.Errors: - if !ok { - return - } - logger.WithError(err).Warn("vocabulary file watcher error") - case _, ok := <-watcher.Events: - if !ok { - return - } - for len(watcher.Events) > 0 { - <-watcher.Events - } - fn() + if fi.ModTime().After(conn.vocabularyFileModTime) { + err = conn.loadVocabularyFile() + if err != nil { + conn.lastVocabularyError = err + return err } + conn.vocabularyFileModTime = fi.ModTime() + conn.lastVocabularyError = nil + logger.Info("vocabulary file reloaded successfully") } + return nil } func (conn *Conn) loadVocabularyFile() error { vf, err := os.ReadFile(conn.cluster.API.VocabularyPath) if err != nil { - return fmt.Errorf("couldn't read vocabulary file %q: %v", conn.cluster.API.VocabularyPath, err) + return fmt.Errorf("couldn't reading the vocabulary file: %v", err) } mk := make([]string, 0, len(conn.cluster.Collections.ManagedProperties)) for k := range conn.cluster.Collections.ManagedProperties { @@ -115,18 +109,24 @@ func (conn *Conn) loadVocabularyFile() error { if err != nil { return fmt.Errorf("while loading vocabulary file %q: %s", conn.cluster.API.VocabularyPath, err) } - err = voc.Validate() - if err != nil { - return fmt.Errorf("while validating vocabulary file %q: %s", conn.cluster.API.VocabularyPath, err) - } conn.vocabularyCache = voc return nil } +// LastVocabularyError returns the last error encountered while loading the +// vocabulary file. +// Implements health.Func +func (conn *Conn) LastVocabularyError() error { + conn.maybeRefreshVocabularyCache(ctxlog.FromContext(context.Background())) + return conn.lastVocabularyError +} + // VocabularyGet refreshes the vocabulary cache if necessary and returns it. func (conn *Conn) VocabularyGet(ctx context.Context) (arvados.Vocabulary, error) { if conn.cluster.API.VocabularyPath == "" { - return arvados.Vocabulary{}, nil + return arvados.Vocabulary{ + Tags: map[string]arvados.VocabularyTag{}, + }, nil } logger := ctxlog.FromContext(ctx) if conn.vocabularyCache == nil { @@ -136,19 +136,10 @@ func (conn *Conn) VocabularyGet(ctx context.Context) (arvados.Vocabulary, error) logger.WithError(err).Error("error loading vocabulary file") return arvados.Vocabulary{}, err } - go watchVocabulary(logger, conn.cluster.API.VocabularyPath, func() { - logger.Info("vocabulary file changed, it'll be reloaded next time it's needed") - conn.reloadVocabulary = true - }) - } else if conn.reloadVocabulary { - // Requested reload of vocabulary file. - conn.reloadVocabulary = false - err := conn.loadVocabularyFile() - if err != nil { - logger.WithError(err).Error("error reloading vocabulary file - ignoring") - } else { - logger.Info("vocabulary file reloaded successfully") - } + } + err := conn.maybeRefreshVocabularyCache(logger) + if err != nil { + logger.WithError(err).Error("error reloading vocabulary file - ignoring") } return *conn.vocabularyCache, nil }