mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 17:14:28 +00:00
Split manager into multiple structs
This commit is contained in:
184
pkg/manager/ports.go
Normal file
184
pkg/manager/ports.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// portAllocator provides efficient port allocation using a bitmap for O(1) operations.
|
||||
// The bitmap approach prevents unbounded memory growth and simplifies port management.
|
||||
type portAllocator struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// Bitmap for O(1) allocation/release
|
||||
// Each bit represents a port (1 = allocated, 0 = free)
|
||||
bitmap []uint64 // Each uint64 covers 64 ports
|
||||
|
||||
// Map port to instance name for cleanup operations
|
||||
allocated map[int]string
|
||||
|
||||
minPort int
|
||||
maxPort int
|
||||
rangeSize int
|
||||
}
|
||||
|
||||
// NewPortAllocator creates a new port allocator for the given port range.
|
||||
// Returns an error if the port range is invalid.
|
||||
func NewPortAllocator(minPort, maxPort int) (*portAllocator, error) {
|
||||
if minPort <= 0 || maxPort <= 0 {
|
||||
return nil, fmt.Errorf("invalid port range: min=%d, max=%d (must be > 0)", minPort, maxPort)
|
||||
}
|
||||
if minPort > maxPort {
|
||||
return nil, fmt.Errorf("invalid port range: min=%d > max=%d", minPort, maxPort)
|
||||
}
|
||||
|
||||
rangeSize := maxPort - minPort + 1
|
||||
bitmapSize := (rangeSize + 63) / 64 // Round up to nearest uint64
|
||||
|
||||
return &portAllocator{
|
||||
bitmap: make([]uint64, bitmapSize),
|
||||
allocated: make(map[int]string),
|
||||
minPort: minPort,
|
||||
maxPort: maxPort,
|
||||
rangeSize: rangeSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Allocate finds and allocates the first available port for the given instance.
|
||||
// Returns the allocated port or an error if no ports are available.
|
||||
func (p *portAllocator) Allocate(instanceName string) (int, error) {
|
||||
if instanceName == "" {
|
||||
return 0, fmt.Errorf("instance name cannot be empty")
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
port, err := p.findFirstFreeBit()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
p.setBit(port)
|
||||
p.allocated[port] = instanceName
|
||||
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// AllocateSpecific allocates a specific port for the given instance.
|
||||
// Returns an error if the port is already allocated or out of range.
|
||||
func (p *portAllocator) AllocateSpecific(port int, instanceName string) error {
|
||||
if instanceName == "" {
|
||||
return fmt.Errorf("instance name cannot be empty")
|
||||
}
|
||||
if port < p.minPort || port > p.maxPort {
|
||||
return fmt.Errorf("port %d is out of range [%d-%d]", port, p.minPort, p.maxPort)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.isBitSet(port) {
|
||||
return fmt.Errorf("port %d is already allocated", port)
|
||||
}
|
||||
|
||||
p.setBit(port)
|
||||
p.allocated[port] = instanceName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release releases a specific port, making it available for reuse.
|
||||
// Returns an error if the port is not allocated.
|
||||
func (p *portAllocator) Release(port int) error {
|
||||
if port < p.minPort || port > p.maxPort {
|
||||
return fmt.Errorf("port %d is out of range [%d-%d]", port, p.minPort, p.maxPort)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if !p.isBitSet(port) {
|
||||
return fmt.Errorf("port %d is not allocated", port)
|
||||
}
|
||||
|
||||
p.clearBit(port)
|
||||
delete(p.allocated, port)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseByInstance releases all ports allocated to the given instance.
|
||||
// This is useful for cleanup when deleting or updating an instance.
|
||||
// Returns the number of ports released.
|
||||
func (p *portAllocator) ReleaseByInstance(instanceName string) int {
|
||||
if instanceName == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
portsToRelease := make([]int, 0)
|
||||
for port, name := range p.allocated {
|
||||
if name == instanceName {
|
||||
portsToRelease = append(portsToRelease, port)
|
||||
}
|
||||
}
|
||||
|
||||
for _, port := range portsToRelease {
|
||||
p.clearBit(port)
|
||||
delete(p.allocated, port)
|
||||
}
|
||||
|
||||
return len(portsToRelease)
|
||||
}
|
||||
|
||||
// --- Internal bitmap operations ---
|
||||
|
||||
// portToBitPos converts a port number to bitmap array index and bit position.
|
||||
func (p *portAllocator) portToBitPos(port int) (index int, bit uint) {
|
||||
offset := port - p.minPort
|
||||
index = offset / 64
|
||||
bit = uint(offset % 64)
|
||||
return
|
||||
}
|
||||
|
||||
// setBit marks a port as allocated in the bitmap.
|
||||
func (p *portAllocator) setBit(port int) {
|
||||
index, bit := p.portToBitPos(port)
|
||||
p.bitmap[index] |= (1 << bit)
|
||||
}
|
||||
|
||||
// clearBit marks a port as free in the bitmap.
|
||||
func (p *portAllocator) clearBit(port int) {
|
||||
index, bit := p.portToBitPos(port)
|
||||
p.bitmap[index] &^= (1 << bit)
|
||||
}
|
||||
|
||||
// isBitSet checks if a port is allocated in the bitmap.
|
||||
func (p *portAllocator) isBitSet(port int) bool {
|
||||
index, bit := p.portToBitPos(port)
|
||||
return (p.bitmap[index] & (1 << bit)) != 0
|
||||
}
|
||||
|
||||
// findFirstFreeBit scans the bitmap to find the first unallocated port.
|
||||
// Returns the port number or an error if no ports are available.
|
||||
func (p *portAllocator) findFirstFreeBit() (int, error) {
|
||||
for i, word := range p.bitmap {
|
||||
if word != ^uint64(0) { // Not all bits are set (some ports are free)
|
||||
// Find the first 0 bit in this word
|
||||
// XOR with all 1s to flip bits, then find first 1 (which was 0)
|
||||
bit := bits.TrailingZeros64(^word)
|
||||
port := p.minPort + (i * 64) + bit
|
||||
|
||||
// Ensure we don't go beyond maxPort due to bitmap rounding
|
||||
if port <= p.maxPort {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no available ports in range [%d-%d]", p.minPort, p.maxPort)
|
||||
}
|
||||
Reference in New Issue
Block a user