@@ -18,78 +18,109 @@ package addr
18
18
19
19
import (
20
20
"fmt"
21
+ "io/fs"
21
22
"net"
22
- "sync"
23
+ "os"
24
+ "path/filepath"
25
+ "strings"
23
26
"time"
27
+
28
+ "sigs.k8s.io/controller-runtime/pkg/internal/flock"
24
29
)
25
30
26
31
// TODO(directxman12): interface / release functionality for external port managers
27
32
28
33
const (
29
- portReserveTime = 1 * time .Minute
34
+ portReserveTime = 10 * time .Minute
30
35
portConflictRetry = 100
36
+ portFilePrefix = "port-"
37
+ )
38
+
39
+ var (
40
+ cacheDir string
31
41
)
32
42
33
- type portCache struct {
34
- lock sync.Mutex
35
- ports map [int ]time.Time
43
+ func init () {
44
+ baseDir , err := os .UserCacheDir ()
45
+ if err != nil {
46
+ baseDir = os .TempDir ()
47
+ }
48
+ cacheDir = filepath .Join (baseDir , "kubebuilder-envtest" )
49
+ if err := os .MkdirAll (cacheDir , 0750 ); err != nil {
50
+ panic (err )
51
+ }
36
52
}
37
53
38
- func (c * portCache ) add (port int ) bool {
39
- c .lock .Lock ()
40
- defer c .lock .Unlock ()
41
- // remove outdated port
42
- for p , t := range c .ports {
43
- if time .Since (t ) > portReserveTime {
44
- delete (c .ports , p )
54
+ type portCache struct {}
55
+
56
+ func (c * portCache ) add (port int ) (bool , error ) {
57
+ // Remove outdated ports.
58
+ if err := fs .WalkDir (os .DirFS (cacheDir ), "." , func (path string , d fs.DirEntry , err error ) error {
59
+ if err != nil {
60
+ return err
61
+ }
62
+ if d .IsDir () || ! d .Type ().IsRegular () || ! strings .HasPrefix (path , portFilePrefix ) {
63
+ return nil
64
+ }
65
+ info , err := d .Info ()
66
+ if err != nil {
67
+ return err
45
68
}
69
+ if time .Since (info .ModTime ()) > portReserveTime {
70
+ if err := os .Remove (filepath .Join (cacheDir , path )); err != nil {
71
+ return err
72
+ }
73
+ }
74
+ return nil
75
+ }); err != nil {
76
+ return false , err
46
77
}
47
- // try allocating new port
48
- if _ , ok := c .ports [port ]; ok {
49
- return false
78
+ // Try allocating new port, by acquiring a file.
79
+ if err := flock .Acquire (fmt .Sprintf ("%s/%s%d" , cacheDir , portFilePrefix , port )); os .IsExist (err ) {
80
+ return false , nil
81
+ } else if err != nil {
82
+ return false , err
50
83
}
51
- c .ports [port ] = time .Now ()
52
- return true
84
+ return true , nil
53
85
}
54
86
55
- var cache = & portCache {
56
- ports : make (map [int ]time.Time ),
57
- }
87
+ var cache = & portCache {}
58
88
59
- func suggest (listenHost string ) (port int , resolvedHost string , err error ) {
89
+ func suggest (listenHost string ) (int , string , error ) {
60
90
if listenHost == "" {
61
91
listenHost = "localhost"
62
92
}
63
93
addr , err := net .ResolveTCPAddr ("tcp" , net .JoinHostPort (listenHost , "0" ))
64
94
if err != nil {
65
- return
95
+ return - 1 , "" , err
66
96
}
67
97
l , err := net .ListenTCP ("tcp" , addr )
68
98
if err != nil {
69
- return
99
+ return - 1 , "" , err
100
+ }
101
+ if err := l .Close (); err != nil {
102
+ return - 1 , "" , err
70
103
}
71
- port = l .Addr ().(* net.TCPAddr ).Port
72
- defer func () {
73
- err = l .Close ()
74
- }()
75
- resolvedHost = addr .IP .String ()
76
- return
104
+ return l .Addr ().(* net.TCPAddr ).Port ,
105
+ addr .IP .String (),
106
+ nil
77
107
}
78
108
79
109
// Suggest suggests an address a process can listen on. It returns
80
110
// a tuple consisting of a free port and the hostname resolved to its IP.
81
111
// It makes sure that new port allocated does not conflict with old ports
82
112
// allocated within 1 minute.
83
- func Suggest (listenHost string ) (port int , resolvedHost string , err error ) {
113
+ func Suggest (listenHost string ) (int , string , error ) {
84
114
for i := 0 ; i < portConflictRetry ; i ++ {
85
- port , resolvedHost , err = suggest (listenHost )
115
+ port , resolvedHost , err : = suggest (listenHost )
86
116
if err != nil {
87
- return
117
+ return - 1 , "" , err
88
118
}
89
- if cache .add (port ) {
90
- return
119
+ if ok , err := cache .add (port ); ok {
120
+ return port , resolvedHost , nil
121
+ } else if err != nil {
122
+ return - 1 , "" , err
91
123
}
92
124
}
93
- err = fmt .Errorf ("no free ports found after %d retries" , portConflictRetry )
94
- return
125
+ return - 1 , "" , fmt .Errorf ("no free ports found after %d retries" , portConflictRetry )
95
126
}
0 commit comments