github.com/ava-labs/subnet-evm@v0.6.4/accounts/keystore/watch.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2016 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 //go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris 28 // +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris 29 30 package keystore 31 32 import ( 33 "os" 34 "time" 35 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/fsnotify/fsnotify" 38 ) 39 40 type watcher struct { 41 ac *accountCache 42 running bool // set to true when runloop begins 43 runEnded bool // set to true when runloop ends 44 starting bool // set to true prior to runloop starting 45 quit chan struct{} 46 } 47 48 func newWatcher(ac *accountCache) *watcher { 49 return &watcher{ 50 ac: ac, 51 quit: make(chan struct{}), 52 } 53 } 54 55 // enabled returns false on systems not supported. 56 func (*watcher) enabled() bool { return true } 57 58 // starts the watcher loop in the background. 59 // Start a watcher in the background if that's not already in progress. 60 // The caller must hold w.ac.mu. 61 func (w *watcher) start() { 62 if w.starting || w.running { 63 return 64 } 65 w.starting = true 66 go w.loop() 67 } 68 69 func (w *watcher) close() { 70 close(w.quit) 71 } 72 73 func (w *watcher) loop() { 74 defer func() { 75 w.ac.mu.Lock() 76 w.running = false 77 w.starting = false 78 w.runEnded = true 79 w.ac.mu.Unlock() 80 }() 81 logger := log.New("path", w.ac.keydir) 82 83 // Create new watcher. 84 watcher, err := fsnotify.NewWatcher() 85 if err != nil { 86 log.Error("Failed to start filesystem watcher", "err", err) 87 return 88 } 89 defer watcher.Close() 90 if err := watcher.Add(w.ac.keydir); err != nil { 91 if !os.IsNotExist(err) { 92 logger.Warn("Failed to watch keystore folder", "err", err) 93 } 94 return 95 } 96 97 logger.Trace("Started watching keystore folder", "folder", w.ac.keydir) 98 defer logger.Trace("Stopped watching keystore folder") 99 100 w.ac.mu.Lock() 101 w.running = true 102 w.ac.mu.Unlock() 103 104 // Wait for file system events and reload. 105 // When an event occurs, the reload call is delayed a bit so that 106 // multiple events arriving quickly only cause a single reload. 107 var ( 108 debounceDuration = 500 * time.Millisecond 109 rescanTriggered = false 110 debounce = time.NewTimer(0) 111 ) 112 // Ignore initial trigger 113 if !debounce.Stop() { 114 <-debounce.C 115 } 116 defer debounce.Stop() 117 for { 118 select { 119 case <-w.quit: 120 return 121 case _, ok := <-watcher.Events: 122 if !ok { 123 return 124 } 125 // Trigger the scan (with delay), if not already triggered 126 if !rescanTriggered { 127 debounce.Reset(debounceDuration) 128 rescanTriggered = true 129 } 130 // The fsnotify library does provide more granular event-info, it 131 // would be possible to refresh individual affected files instead 132 // of scheduling a full rescan. For most cases though, the 133 // full rescan is quick and obviously simplest. 134 case err, ok := <-watcher.Errors: 135 if !ok { 136 return 137 } 138 log.Info("Filsystem watcher error", "err", err) 139 case <-debounce.C: 140 w.ac.scanAccounts() 141 rescanTriggered = false 142 } 143 } 144 }