Browse code

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>

Sebastiaan van Stijn authored on 2026/03/29 23:14:38
Showing 1 changed files
... ...
@@ -14,48 +14,55 @@ import (
14 14
 	"github.com/vishvananda/netns"
15 15
 )
16 16
 
17
-var (
18
-	initNs   = netns.None()
19
-	initNl   nlwrap.Handle
20
-	initOnce sync.Once
21
-	// NetlinkSocketsTimeout represents the default timeout duration for the sockets
22
-	NetlinkSocketsTimeout = 3 * time.Second
23
-)
17
+// NetlinkSocketsTimeout represents the default timeout duration for the sockets.
18
+var NetlinkSocketsTimeout = 3 * time.Second
19
+
20
+// initNamespace initializes a new network namespace.
21
+var initNamespace = sync.OnceValues(initHandles)
24 22
 
25 23
 // initHandles initializes a new network namespace
26
-func initHandles() {
27
-	var err error
28
-	initNs, err = netns.Get()
24
+func initHandles() (netns.NsHandle, nlwrap.Handle) {
25
+	initNs, err := netns.Get()
29 26
 	if err != nil {
30
-		log.G(context.TODO()).Errorf("could not get initial namespace: %v", err)
27
+		log.G(context.Background()).WithError(err).Error("could not get initial namespace: falling back to using netns.None")
28
+		initNs = netns.None()
31 29
 	}
32
-	initNl, err = nlwrap.NewHandle(getSupportedNlFamilies()...)
30
+	initNl, err := nlwrap.NewHandle(getSupportedNlFamilies()...)
33 31
 	if err != nil {
34 32
 		// Fail fast to keep the invariant: NlHandle must be a valid handle
35 33
 		panic(fmt.Errorf("could not create netlink handle on initial (host) namespace: %w", err))
36 34
 	}
37 35
 	err = initNl.SetSocketTimeout(NetlinkSocketsTimeout)
38 36
 	if err != nil {
39
-		log.G(context.TODO()).Warnf("Failed to set the timeout on the default netlink handle sockets: %v", err)
37
+		log.G(context.Background()).WithError(err).Warn("failed to set the timeout on the default netlink handle sockets")
40 38
 	}
39
+
40
+	return initNs, initNl
41 41
 }
42 42
 
43 43
 // ResetHandles resets the initial namespace and netlink handles.
44 44
 // This is useful for testing to ensure a clean state. It will
45 45
 // panic if called outside a test.
46
+//
47
+// Note: This function is not safe for concurrent use with callers
48
+// that are using handles obtained from this package. It may close
49
+// handles while they are still in use.
46 50
 func ResetHandles() {
47 51
 	if !testing.Testing() {
48 52
 		panic("ResetHandles should only be called from tests")
49 53
 	}
54
+	initNs, initNl := initNamespace()
55
+	// Reset the initNamespace sync.OnceValues. This may race with
56
+	// concurrent callers still calling the old initNamespace (and
57
+	// values), but adding a [sync.RWMutex] only for the test-case
58
+	// is probably too much (unless things are racy).
59
+	initNamespace = sync.OnceValues(initHandles)
50 60
 	if initNs.IsOpen() {
51 61
 		_ = initNs.Close()
52
-		initNs = netns.None()
53 62
 	}
54 63
 	if initNl.Handle != nil {
55 64
 		initNl.Close()
56
-		initNl = nlwrap.Handle{}
57 65
 	}
58
-	initOnce = sync.Once{}
59 66
 }
60 67
 
61 68
 // ParseHandlerInt transforms the namespace handler into an integer
... ...
@@ -65,14 +72,14 @@ func ParseHandlerInt() int {
65 65
 
66 66
 // GetHandler returns the namespace handler
67 67
 func getHandler() netns.NsHandle {
68
-	initOnce.Do(initHandles)
69
-	return initNs
68
+	ns, _ := initNamespace()
69
+	return ns
70 70
 }
71 71
 
72 72
 // NlHandle returns the netlink handler
73 73
 func NlHandle() nlwrap.Handle {
74
-	initOnce.Do(initHandles)
75
-	return initNl
74
+	_, nl := initNamespace()
75
+	return nl
76 76
 }
77 77
 
78 78
 func getSupportedNlFamilies() []int {