From abd14b6ddc91a2c6e27d25c45514ebb7d37dc12d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 29 Nov 2017 10:26:18 +0100 Subject: [PATCH] accounts/keystore: don't scan all files, only file affected by event --- accounts/keystore/account_cache.go | 105 ++++++++++++++++++++--------- accounts/keystore/file_cache.go | 29 ++++++++ accounts/keystore/watch.go | 40 +++++------ 3 files changed, 124 insertions(+), 50 deletions(-) diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 71f698ece7..f1707576ec 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -228,8 +228,77 @@ func (ac *accountCache) close() { ac.mu.Unlock() } +func readAccount(path string, buf *bufio.Reader) *accounts.Account { + + var key struct { + Address string `json:"address"` + } + fd, err := os.Open(path) + if err != nil { + log.Trace("Failed to open keystore file", "path", path, "err", err) + return nil + } + defer fd.Close() + buf.Reset(fd) + // Parse the address. + key.Address = "" + err = json.NewDecoder(buf).Decode(&key) + addr := common.HexToAddress(key.Address) + switch { + case err != nil: + log.Debug("Failed to decode keystore key", "path", path, "err", err) + case (addr == common.Address{}): + log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") + default: + return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}} + } + return nil + +} + +// checkFile can be used when a file notification triggered some kind of change on a file +// in the keystore directory. The method checks what happened (change/delete/remove/nothing) and +// updates the keystore accordingly +func (ac *accountCache) checkFile(path string) error { + + start := time.Now() + created, deleted, updated, err := ac.fileC.checkFile(path) + + if err != nil { + log.Debug("Failed to reload keystore contents", "err", err) + return err + } + if !created && !deleted && !updated { + return nil + } + var ( + buf = new(bufio.Reader) + ) + switch { + case created: + if a := readAccount(path, buf); a != nil { + ac.add(*a) + } + case deleted: + ac.deleteByFile(path) + case updated: + ac.deleteByFile(path) + if a := readAccount(path, buf); a != nil { + ac.add(*a) + } + } + select { + case ac.notify <- struct{}{}: + default: + } + end := time.Now() + log.Trace("Handled keystore changes", "time", end.Sub(start)) + return nil +} + // scanAccounts checks if any changes have occurred on the filesystem, and // updates the account cache accordingly + func (ac *accountCache) scanAccounts() error { // Scan the entire folder metadata for file changes creates, deletes, updates, err := ac.fileC.scan(ac.keydir) @@ -240,40 +309,14 @@ func (ac *accountCache) scanAccounts() error { if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 { return nil } - // Create a helper method to scan the contents of the key files - var ( - buf = new(bufio.Reader) - key struct { - Address string `json:"address"` - } - ) - readAccount := func(path string) *accounts.Account { - fd, err := os.Open(path) - if err != nil { - log.Trace("Failed to open keystore file", "path", path, "err", err) - return nil - } - defer fd.Close() - buf.Reset(fd) - // Parse the address. - key.Address = "" - err = json.NewDecoder(buf).Decode(&key) - addr := common.HexToAddress(key.Address) - switch { - case err != nil: - log.Debug("Failed to decode keystore key", "path", path, "err", err) - case (addr == common.Address{}): - log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") - default: - return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}} - } - return nil - } // Process all the file diffs start := time.Now() + var ( + buf = new(bufio.Reader) + ) for _, p := range creates.List() { - if a := readAccount(p.(string)); a != nil { + if a := readAccount(p.(string),buf); a != nil { ac.add(*a) } } @@ -283,7 +326,7 @@ func (ac *accountCache) scanAccounts() error { for _, p := range updates.List() { path := p.(string) ac.deleteByFile(path) - if a := readAccount(path); a != nil { + if a := readAccount(path, buf); a != nil { ac.add(*a) } } diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go index c91b7b7b61..96bd6368d1 100644 --- a/accounts/keystore/file_cache.go +++ b/accounts/keystore/file_cache.go @@ -35,6 +35,35 @@ type fileCache struct { mu sync.RWMutex } +func (fc *fileCache) checkFile(path string) (created, deleted, updated bool, err error) { + + fc.mu.Lock() + defer fc.mu.Unlock() + + created, deleted, updated, err = false, false, false, nil + + previouslyKnown := fc.all.Has(path) + + fi, err := os.Lstat(path) + + if err != nil { + //A file has been deleted, but it can be a file which we + // were not previously watching. + deleted = previouslyKnown && os.IsNotExist(err) + return created, deleted, updated, err + } + + if skipKeyFile(fi) { + log.Trace("Ignoring file on account scan", "path", path) + return created, deleted, updated, err + } + + created = !previouslyKnown + updated = previouslyKnown + + return created, deleted, updated, nil +} + // scan performs a new scan on the given directory, compares against the already // cached filenames, and returns file sets: creates, deletes, updates. func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) { diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go index bbcfb99257..a814f929fd 100644 --- a/accounts/keystore/watch.go +++ b/accounts/keystore/watch.go @@ -19,8 +19,6 @@ package keystore import ( - "time" - "github.com/ethereum/go-ethereum/log" "github.com/rjeczalik/notify" ) @@ -80,29 +78,33 @@ func (w *watcher) loop() { // Wait for file system events and reload. // When an event occurs, the reload call is delayed a bit so that // multiple events arriving quickly only cause a single reload. - var ( - debounceDuration = 500 * time.Millisecond - rescanTriggered = false - debounce = time.NewTimer(0) - ) + //var ( + //debounceDuration = 500 * time.Millisecond + //rescanTriggered = false + //debounce = time.NewTimer(0) + //) // Ignore initial trigger - if !debounce.Stop() { - <-debounce.C - } - defer debounce.Stop() + //if !debounce.Stop() { + // <-debounce.C + //} + //defer debounce.Stop() for { select { case <-w.quit: return - case <-w.ev: + case ei := <-w.ev: +// fmt.Printf("Event: %v\n", ei) + ei.Path() + w.ac.checkFile(ei.Path()) + // w.ac.scanAccounts() // Trigger the scan (with delay), if not already triggered - if !rescanTriggered { - debounce.Reset(debounceDuration) - rescanTriggered = true - } - case <-debounce.C: - w.ac.scanAccounts() - rescanTriggered = false + // if !rescanTriggered { + // debounce.Reset(debounceDuration) + // rescanTriggered = true + // } + // case <-debounce.C: + // w.ac.scanAccounts() + // rescanTriggered = false } } }