From 1e1677b35a3646b57afad25f4d6548a5169a6c82 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 22 Aug 2025 22:23:50 +0000 Subject: [PATCH] Fix: linter errors --- internal/audio/input_ipc.go | 40 ++++++++++++++--------------- internal/audio/mic_contention.go | 44 ++++++++++++++++---------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/internal/audio/input_ipc.go b/internal/audio/input_ipc.go index 0050efc..6a33458 100644 --- a/internal/audio/input_ipc.go +++ b/internal/audio/input_ipc.go @@ -16,9 +16,9 @@ import ( const ( inputMagicNumber uint32 = 0x4A4B4D49 // "JKMI" (JetKVM Microphone Input) inputSocketName = "audio_input.sock" - maxFrameSize = 4096 // Maximum Opus frame size + maxFrameSize = 4096 // Maximum Opus frame size writeTimeout = 5 * time.Millisecond // Non-blocking write timeout - maxDroppedFrames = 100 // Maximum consecutive dropped frames before reconnect + maxDroppedFrames = 100 // Maximum consecutive dropped frames before reconnect ) // InputMessageType represents the type of IPC message @@ -55,17 +55,17 @@ type AudioInputServer struct { processingTime int64 // Average processing time in nanoseconds (atomic) droppedFrames int64 // Dropped frames counter (atomic) totalFrames int64 // Total frames counter (atomic) - + listener net.Listener conn net.Conn mtx sync.Mutex running bool // Triple-goroutine architecture - messageChan chan *InputIPCMessage // Buffered channel for incoming messages - processChan chan *InputIPCMessage // Buffered channel for processing queue - stopChan chan struct{} // Stop signal for all goroutines - wg sync.WaitGroup // Wait group for goroutine coordination + messageChan chan *InputIPCMessage // Buffered channel for incoming messages + processChan chan *InputIPCMessage // Buffered channel for processing queue + stopChan chan struct{} // Stop signal for all goroutines + wg sync.WaitGroup // Wait group for goroutine coordination } // NewAudioInputServer creates a new audio input server @@ -315,10 +315,10 @@ type AudioInputClient struct { // Atomic fields must be first for proper alignment on ARM droppedFrames int64 // Atomic counter for dropped frames totalFrames int64 // Atomic counter for total frames - - conn net.Conn - mtx sync.Mutex - running bool + + conn net.Conn + mtx sync.Mutex + running bool } // NewAudioInputClient creates a new audio input client @@ -575,7 +575,7 @@ func (ais *AudioInputServer) startProcessorGoroutine() { // Check if processing queue is getting full queueLen := len(ais.processChan) bufferSize := int(atomic.LoadInt64(&ais.bufferSize)) - + if queueLen > bufferSize*3/4 { // Drop oldest frames, keep newest select { @@ -585,7 +585,7 @@ func (ais *AudioInputServer) startProcessorGoroutine() { } } } - + // Send to processing queue select { case ais.processChan <- msg: @@ -605,7 +605,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() { defer ais.wg.Done() ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() - + for { select { case <-ais.stopChan: @@ -618,7 +618,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() { start := time.Now() err := ais.processMessage(msg) processingTime := time.Since(start).Nanoseconds() - + // Calculate end-to-end latency using message timestamp if msg.Type == InputMessageTypeOpusFrame && msg.Timestamp > 0 { msgTime := time.Unix(0, msg.Timestamp) @@ -634,7 +634,7 @@ func (ais *AudioInputServer) startMonitorGoroutine() { newAvg := (currentAvg + processingTime) / 2 atomic.StoreInt64(&ais.processingTime, newAvg) } - + if err != nil { atomic.AddInt64(&ais.droppedFrames, 1) } @@ -643,12 +643,12 @@ func (ais *AudioInputServer) startMonitorGoroutine() { goto adaptiveBuffering } } - - adaptiveBuffering: + + adaptiveBuffering: // Adaptive buffer sizing based on processing time avgTime := atomic.LoadInt64(&ais.processingTime) currentSize := atomic.LoadInt64(&ais.bufferSize) - + if avgTime > 10*1000*1000 { // > 10ms processing time // Increase buffer size newSize := currentSize * 2 @@ -685,4 +685,4 @@ func getInputSocketPath() string { return path } return filepath.Join("/var/run", inputSocketName) -} \ No newline at end of file +} diff --git a/internal/audio/mic_contention.go b/internal/audio/mic_contention.go index 6c35393..9df63e2 100644 --- a/internal/audio/mic_contention.go +++ b/internal/audio/mic_contention.go @@ -10,10 +10,10 @@ import ( // with reduced contention using atomic operations and conditional locking type MicrophoneContentionManager struct { // Atomic fields (must be 64-bit aligned on 32-bit systems) - lastOpNano int64 // Unix nanoseconds of last operation - cooldownNanos int64 // Cooldown duration in nanoseconds - operationID int64 // Incremental operation ID for tracking - + lastOpNano int64 // Unix nanoseconds of last operation + cooldownNanos int64 // Cooldown duration in nanoseconds + operationID int64 // Incremental operation ID for tracking + // Lock-free state flags (using atomic.Pointer for lock-free updates) lockPtr unsafe.Pointer // *sync.Mutex - conditionally allocated } @@ -27,61 +27,61 @@ func NewMicrophoneContentionManager(cooldown time.Duration) *MicrophoneContentio // OperationResult represents the result of attempting a microphone operation type OperationResult struct { - Allowed bool + Allowed bool RemainingCooldown time.Duration - OperationID int64 + OperationID int64 } // TryOperation attempts to perform a microphone operation with optimized contention handling func (mcm *MicrophoneContentionManager) TryOperation() OperationResult { now := time.Now().UnixNano() cooldown := atomic.LoadInt64(&mcm.cooldownNanos) - + // Fast path: check if we're clearly outside cooldown period using atomic read lastOp := atomic.LoadInt64(&mcm.lastOpNano) elapsed := now - lastOp - + if elapsed >= cooldown { // Attempt atomic update without locking if atomic.CompareAndSwapInt64(&mcm.lastOpNano, lastOp, now) { opID := atomic.AddInt64(&mcm.operationID, 1) return OperationResult{ - Allowed: true, + Allowed: true, RemainingCooldown: 0, - OperationID: opID, + OperationID: opID, } } } - + // Slow path: potential contention, check remaining cooldown currentLastOp := atomic.LoadInt64(&mcm.lastOpNano) currentElapsed := now - currentLastOp - + if currentElapsed >= cooldown { // Race condition: another operation might have updated lastOpNano // Try once more with CAS if atomic.CompareAndSwapInt64(&mcm.lastOpNano, currentLastOp, now) { opID := atomic.AddInt64(&mcm.operationID, 1) return OperationResult{ - Allowed: true, + Allowed: true, RemainingCooldown: 0, - OperationID: opID, + OperationID: opID, } } // If CAS failed, fall through to cooldown calculation currentLastOp = atomic.LoadInt64(&mcm.lastOpNano) currentElapsed = now - currentLastOp } - + remaining := time.Duration(cooldown - currentElapsed) if remaining < 0 { remaining = 0 } - + return OperationResult{ - Allowed: false, + Allowed: false, RemainingCooldown: remaining, - OperationID: atomic.LoadInt64(&mcm.operationID), + OperationID: atomic.LoadInt64(&mcm.operationID), } } @@ -127,20 +127,20 @@ func GetMicrophoneContentionManager() *MicrophoneContentionManager { if ptr != nil { return (*MicrophoneContentionManager)(ptr) } - + // Initialize on first use if atomic.CompareAndSwapInt32(&micContentionInitialized, 0, 1) { manager := NewMicrophoneContentionManager(200 * time.Millisecond) atomic.StorePointer(&globalMicContentionManager, unsafe.Pointer(manager)) return manager } - + // Another goroutine initialized it, try again ptr = atomic.LoadPointer(&globalMicContentionManager) if ptr != nil { return (*MicrophoneContentionManager)(ptr) } - + // Fallback: create a new manager (should rarely happen) return NewMicrophoneContentionManager(200 * time.Millisecond) } @@ -155,4 +155,4 @@ func TryMicrophoneOperation() OperationResult { func SetMicrophoneCooldown(cooldown time.Duration) { manager := GetMicrophoneContentionManager() manager.SetCooldown(cooldown) -} \ No newline at end of file +}