From b598a7fc5d167fe5be137d2c5cc9f1f6a01636ef Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 4 Jan 2016 21:04:01 -0500 Subject: [PATCH] net: check /etc/hosts for modifications every 5 seconds, not 5 minutes But also cache the previous parsed form and don't reread if the size and modification time are both unchanged from before. On systems with stable /etc/hosts this should result in more stat calls but only a single parsing of /etc/hosts. On systems with variable /etc/hosts files (like some Docker systems) this should result in quicker adoption of changes. Fixes #13340. Change-Id: Iba93b204be73d6d903cd17c58038a4fcfd0952b9 Reviewed-on: https://go-review.googlesource.com/18258 Reviewed-by: Brad Fitzpatrick --- src/net/hosts.go | 84 +++++++++++++++++++++++++++--------------------- src/net/parse.go | 9 ++++++ 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/net/hosts.go b/src/net/hosts.go index 577dba9cb9..c4de1b6a97 100644 --- a/src/net/hosts.go +++ b/src/net/hosts.go @@ -9,7 +9,7 @@ import ( "time" ) -const cacheMaxAge = 5 * time.Minute +const cacheMaxAge = 5 * time.Second func parseLiteralIP(addr string) string { var ip IP @@ -44,47 +44,59 @@ var hosts struct { expire time.Time path string + mtime time.Time + size int64 } func readHosts() { now := time.Now() hp := testHookHostsPath - if len(hosts.byName) == 0 || now.After(hosts.expire) || hosts.path != hp { - hs := make(map[string][]string) - is := make(map[string][]string) - var file *file - if file, _ = open(hp); file == nil { - return - } - for line, ok := file.readLine(); ok; line, ok = file.readLine() { - if i := byteIndex(line, '#'); i >= 0 { - // Discard comments. - line = line[0:i] - } - f := getFields(line) - if len(f) < 2 { - continue - } - addr := parseLiteralIP(f[0]) - if addr == "" { - continue - } - for i := 1; i < len(f); i++ { - name := absDomainName([]byte(f[i])) - h := []byte(f[i]) - lowerASCIIBytes(h) - key := absDomainName(h) - hs[key] = append(hs[key], addr) - is[addr] = append(is[addr], name) - } - } - // Update the data cache. - hosts.expire = now.Add(cacheMaxAge) - hosts.path = hp - hosts.byName = hs - hosts.byAddr = is - file.close() + + if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 { + return } + mtime, size, err := stat(hp) + if err == nil && hosts.path == hp && hosts.mtime.Equal(mtime) && hosts.size == size { + hosts.expire = now.Add(cacheMaxAge) + return + } + + hs := make(map[string][]string) + is := make(map[string][]string) + var file *file + if file, _ = open(hp); file == nil { + return + } + for line, ok := file.readLine(); ok; line, ok = file.readLine() { + if i := byteIndex(line, '#'); i >= 0 { + // Discard comments. + line = line[0:i] + } + f := getFields(line) + if len(f) < 2 { + continue + } + addr := parseLiteralIP(f[0]) + if addr == "" { + continue + } + for i := 1; i < len(f); i++ { + name := absDomainName([]byte(f[i])) + h := []byte(f[i]) + lowerASCIIBytes(h) + key := absDomainName(h) + hs[key] = append(hs[key], addr) + is[addr] = append(is[addr], name) + } + } + // Update the data cache. + hosts.expire = now.Add(cacheMaxAge) + hosts.path = hp + hosts.byName = hs + hosts.byAddr = is + hosts.mtime = mtime + hosts.size = size + file.close() } // lookupStaticHost looks up the addresses for the given host from /etc/hosts. diff --git a/src/net/parse.go b/src/net/parse.go index 93cdf8d73f..eaaa1edf30 100644 --- a/src/net/parse.go +++ b/src/net/parse.go @@ -10,6 +10,7 @@ package net import ( "io" "os" + "time" _ "unsafe" // For go:linkname ) @@ -71,6 +72,14 @@ func open(name string) (*file, error) { return &file{fd, make([]byte, 0, os.Getpagesize()), false}, nil } +func stat(name string) (mtime time.Time, size int64, err error) { + st, err := os.Stat(name) + if err != nil { + return time.Time{}, 0, err + } + return st.ModTime(), st.Size(), nil +} + // byteIndex is strings.IndexByte. It returns the index of the // first instance of c in s, or -1 if c is not present in s. // strings.IndexByte is implemented in runtime/asm_$GOARCH.s