main
go 104 lines 2.09 KB
Raw
1 package transport
2
3 import (
4 "errors"
5 "sort"
6 "sync"
7 "time"
8 )
9
10 var ErrPortExhausted = errors.New("no ports available")
11
12 type portReservation struct {
13 port int
14 expiresAt time.Time
15 }
16
17 // PortAllocator manages a pool of ports for dynamic per-lease allocation.
18 type PortAllocator struct {
19 available []int
20 inUse map[int]string
21 reserved map[string]portReservation
22 grace time.Duration
23 mu sync.Mutex
24 }
25
26 func NewPortAllocator(min, max int, grace time.Duration) *PortAllocator {
27 if min <= 0 || max <= 0 || min > max {
28 return &PortAllocator{
29 inUse: make(map[int]string),
30 reserved: make(map[string]portReservation),
31 grace: grace,
32 }
33 }
34
35 available := make([]int, 0, max-min+1)
36 for port := min; port <= max; port++ {
37 available = append(available, port)
38 }
39 return &PortAllocator{
40 available: available,
41 inUse: make(map[int]string),
42 reserved: make(map[string]portReservation),
43 grace: grace,
44 }
45 }
46
47 func (a *PortAllocator) Allocate(name string) (int, error) {
48 a.mu.Lock()
49 defer a.mu.Unlock()
50
51 a.cleanupExpiredLocked(time.Now())
52
53 if res, ok := a.reserved[name]; ok {
54 delete(a.reserved, name)
55 a.inUse[res.port] = name
56 return res.port, nil
57 }
58
59 if len(a.available) == 0 {
60 return 0, ErrPortExhausted
61 }
62
63 port := a.available[0]
64 a.available = a.available[1:]
65 a.inUse[port] = name
66 return port, nil
67 }
68
69 func (a *PortAllocator) Release(port int) {
70 a.mu.Lock()
71 defer a.mu.Unlock()
72
73 name, ok := a.inUse[port]
74 if !ok {
75 return
76 }
77 delete(a.inUse, port)
78
79 if prev, exists := a.reserved[name]; exists {
80 a.sortedInsertLocked(prev.port)
81 }
82
83 a.reserved[name] = portReservation{
84 port: port,
85 expiresAt: time.Now().Add(a.grace),
86 }
87 a.cleanupExpiredLocked(time.Now())
88 }
89
90 func (a *PortAllocator) cleanupExpiredLocked(now time.Time) {
91 for name, res := range a.reserved {
92 if now.After(res.expiresAt) {
93 delete(a.reserved, name)
94 a.sortedInsertLocked(res.port)
95 }
96 }
97 }
98
99 func (a *PortAllocator) sortedInsertLocked(port int) {
100 i := sort.SearchInts(a.available, port)
101 a.available = append(a.available, 0)
102 copy(a.available[i+1:], a.available[i:])
103 a.available[i] = port
104 }