Commit 9fc22d29 authored by Michael Stapelberg's avatar Michael Stapelberg Committed by Mikio Hara

net: update zoneCache on cache misses to cover appearing interfaces

performance differences are in measurement noise as per benchcmp:

benchmark                            old ns/op     new ns/op     delta
BenchmarkUDP6LinkLocalUnicast-12     5012          5009          -0.06%

Fixes #28535

Change-Id: Id022e2ed089ce8388a2398e755848ec94e77e653
Reviewed-on: https://go-review.googlesource.com/c/146941
Run-TryBot: Mikio Hara <mikioh.public.networking@gmail.com>
Reviewed-by: 's avatarMikio Hara <mikioh.public.networking@gmail.com>
parent 5848b6c9
...@@ -102,7 +102,7 @@ func Interfaces() ([]Interface, error) { ...@@ -102,7 +102,7 @@ func Interfaces() ([]Interface, error) {
return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
} }
if len(ift) != 0 { if len(ift) != 0 {
zoneCache.update(ift) zoneCache.update(ift, false)
} }
return ift, nil return ift, nil
} }
...@@ -159,7 +159,7 @@ func InterfaceByName(name string) (*Interface, error) { ...@@ -159,7 +159,7 @@ func InterfaceByName(name string) (*Interface, error) {
return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err} return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
} }
if len(ift) != 0 { if len(ift) != 0 {
zoneCache.update(ift) zoneCache.update(ift, false)
} }
for _, ifi := range ift { for _, ifi := range ift {
if name == ifi.Name { if name == ifi.Name {
...@@ -187,18 +187,21 @@ var zoneCache = ipv6ZoneCache{ ...@@ -187,18 +187,21 @@ var zoneCache = ipv6ZoneCache{
toName: make(map[int]string), toName: make(map[int]string),
} }
func (zc *ipv6ZoneCache) update(ift []Interface) { // update refreshes the network interface information if the cache was last
// updated more than 1 minute ago, or if force is set. It returns whether the
// cache was updated.
func (zc *ipv6ZoneCache) update(ift []Interface, force bool) (updated bool) {
zc.Lock() zc.Lock()
defer zc.Unlock() defer zc.Unlock()
now := time.Now() now := time.Now()
if zc.lastFetched.After(now.Add(-60 * time.Second)) { if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
return return false
} }
zc.lastFetched = now zc.lastFetched = now
if len(ift) == 0 { if len(ift) == 0 {
var err error var err error
if ift, err = interfaceTable(0); err != nil { if ift, err = interfaceTable(0); err != nil {
return return false
} }
} }
zc.toIndex = make(map[string]int, len(ift)) zc.toIndex = make(map[string]int, len(ift))
...@@ -209,16 +212,25 @@ func (zc *ipv6ZoneCache) update(ift []Interface) { ...@@ -209,16 +212,25 @@ func (zc *ipv6ZoneCache) update(ift []Interface) {
zc.toName[ifi.Index] = ifi.Name zc.toName[ifi.Index] = ifi.Name
} }
} }
return true
} }
func (zc *ipv6ZoneCache) name(index int) string { func (zc *ipv6ZoneCache) name(index int) string {
if index == 0 { if index == 0 {
return "" return ""
} }
zoneCache.update(nil) updated := zoneCache.update(nil, false)
zoneCache.RLock() zoneCache.RLock()
defer zoneCache.RUnlock()
name, ok := zoneCache.toName[index] name, ok := zoneCache.toName[index]
zoneCache.RUnlock()
if !ok {
if !updated {
zoneCache.update(nil, true)
zoneCache.RLock()
name, ok = zoneCache.toName[index]
zoneCache.RUnlock()
}
}
if !ok { if !ok {
name = uitoa(uint(index)) name = uitoa(uint(index))
} }
...@@ -229,10 +241,18 @@ func (zc *ipv6ZoneCache) index(name string) int { ...@@ -229,10 +241,18 @@ func (zc *ipv6ZoneCache) index(name string) int {
if name == "" { if name == "" {
return 0 return 0
} }
zoneCache.update(nil) updated := zoneCache.update(nil, false)
zoneCache.RLock() zoneCache.RLock()
defer zoneCache.RUnlock()
index, ok := zoneCache.toIndex[name] index, ok := zoneCache.toIndex[name]
zoneCache.RUnlock()
if !ok {
if !updated {
zoneCache.update(nil, true)
zoneCache.RLock()
index, ok = zoneCache.toIndex[name]
zoneCache.RUnlock()
}
}
if !ok { if !ok {
index, _, _ = dtoi(name) index, _, _ = dtoi(name)
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
package net package net
import ( import (
"errors"
"fmt" "fmt"
"os/exec" "os/exec"
"runtime" "runtime"
...@@ -53,3 +54,7 @@ func (ti *testInterface) setPointToPoint(suffix int) error { ...@@ -53,3 +54,7 @@ func (ti *testInterface) setPointToPoint(suffix int) error {
}) })
return nil return nil
} }
func (ti *testInterface) setLinkLocal(suffix int) error {
return errors.New("not yet implemented for BSD")
}
...@@ -35,6 +35,31 @@ func (ti *testInterface) setBroadcast(suffix int) error { ...@@ -35,6 +35,31 @@ func (ti *testInterface) setBroadcast(suffix int) error {
return nil return nil
} }
func (ti *testInterface) setLinkLocal(suffix int) error {
ti.name = fmt.Sprintf("gotest%d", suffix)
xname, err := exec.LookPath("ip")
if err != nil {
return err
}
ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
Path: xname,
Args: []string{"ip", "link", "add", ti.name, "type", "dummy"},
})
ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
Path: xname,
Args: []string{"ip", "address", "add", ti.local, "dev", ti.name},
})
ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
Path: xname,
Args: []string{"ip", "address", "del", ti.local, "dev", ti.name},
})
ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
Path: xname,
Args: []string{"ip", "link", "delete", ti.name, "type", "dummy"},
})
return nil
}
func (ti *testInterface) setPointToPoint(suffix int) error { func (ti *testInterface) setPointToPoint(suffix int) error {
ti.name = fmt.Sprintf("gotest%d", suffix) ti.name = fmt.Sprintf("gotest%d", suffix)
xname, err := exec.LookPath("ip") xname, err := exec.LookPath("ip")
......
...@@ -176,3 +176,37 @@ func TestInterfaceArrivalAndDeparture(t *testing.T) { ...@@ -176,3 +176,37 @@ func TestInterfaceArrivalAndDeparture(t *testing.T) {
} }
} }
} }
func TestInterfaceArrivalAndDepartureZoneCache(t *testing.T) {
if testing.Short() {
t.Skip("avoid external network")
}
if os.Getuid() != 0 {
t.Skip("must be root")
}
// Ensure zoneCache is filled:
_, _ = Listen("tcp", "[fe80::1%nonexistant]:0")
ti := &testInterface{local: "fe80::1"}
if err := ti.setLinkLocal(0); err != nil {
t.Skipf("test requires external command: %v", err)
}
if err := ti.setup(); err != nil {
t.Fatal(err)
}
defer ti.teardown()
time.Sleep(3 * time.Millisecond)
// If Listen fails (on Linux with “bind: invalid argument”), zoneCache was
// not updated when encountering a nonexistant interface:
ln, err := Listen("tcp", "[fe80::1%"+ti.name+"]:0")
if err != nil {
t.Fatal(err)
}
ln.Close()
if err := ti.teardown(); err != nil {
t.Fatal(err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment