daemon/libnetwork/ns: use sync.OnceValues

This avoids depending on global state in the package, and "forces"
consumers to go through the initHandles() func to get the handles.

A slight change in behavior is that `ResetHandles()` may now initialize
a new namespace only to reset it, but this is likely an "OK" trade-off.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-03-29 16:14:38 +02:00
parent 635f4c3dc4
commit af73684bc3

View File

@@ -14,48 +14,55 @@ import (
"github.com/vishvananda/netns"
)
var (
initNs = netns.None()
initNl nlwrap.Handle
initOnce sync.Once
// NetlinkSocketsTimeout represents the default timeout duration for the sockets
NetlinkSocketsTimeout = 3 * time.Second
)
// NetlinkSocketsTimeout represents the default timeout duration for the sockets.
var NetlinkSocketsTimeout = 3 * time.Second
// initNamespace initializes a new network namespace.
var initNamespace = sync.OnceValues(initHandles)
// initHandles initializes a new network namespace
func initHandles() {
var err error
initNs, err = netns.Get()
func initHandles() (netns.NsHandle, nlwrap.Handle) {
initNs, err := netns.Get()
if err != nil {
log.G(context.TODO()).Errorf("could not get initial namespace: %v", err)
log.G(context.Background()).WithError(err).Error("could not get initial namespace: falling back to using netns.None")
initNs = netns.None()
}
initNl, err = nlwrap.NewHandle(getSupportedNlFamilies()...)
initNl, err := nlwrap.NewHandle(getSupportedNlFamilies()...)
if err != nil {
// Fail fast to keep the invariant: NlHandle must be a valid handle
panic(fmt.Errorf("could not create netlink handle on initial (host) namespace: %w", err))
}
err = initNl.SetSocketTimeout(NetlinkSocketsTimeout)
if err != nil {
log.G(context.TODO()).Warnf("Failed to set the timeout on the default netlink handle sockets: %v", err)
log.G(context.Background()).WithError(err).Warn("failed to set the timeout on the default netlink handle sockets")
}
return initNs, initNl
}
// ResetHandles resets the initial namespace and netlink handles.
// This is useful for testing to ensure a clean state. It will
// panic if called outside a test.
//
// Note: This function is not safe for concurrent use with callers
// that are using handles obtained from this package. It may close
// handles while they are still in use.
func ResetHandles() {
if !testing.Testing() {
panic("ResetHandles should only be called from tests")
}
initNs, initNl := initNamespace()
// Reset the initNamespace sync.OnceValues. This may race with
// concurrent callers still calling the old initNamespace (and
// values), but adding a [sync.RWMutex] only for the test-case
// is probably too much (unless things are racy).
initNamespace = sync.OnceValues(initHandles)
if initNs.IsOpen() {
_ = initNs.Close()
initNs = netns.None()
}
if initNl.Handle != nil {
initNl.Close()
initNl = nlwrap.Handle{}
}
initOnce = sync.Once{}
}
// ParseHandlerInt transforms the namespace handler into an integer
@@ -65,14 +72,14 @@ func ParseHandlerInt() int {
// GetHandler returns the namespace handler
func getHandler() netns.NsHandle {
initOnce.Do(initHandles)
return initNs
ns, _ := initNamespace()
return ns
}
// NlHandle returns the netlink handler
func NlHandle() nlwrap.Handle {
initOnce.Do(initHandles)
return initNl
_, nl := initNamespace()
return nl
}
func getSupportedNlFamilies() []int {