fixup! Weekend fun with further optimisations.

This commit is contained in:
2025-09-14 01:40:03 +01:00
parent 7457a901a9
commit 0d88612d8c
3 changed files with 213 additions and 2 deletions
+6
View File
@@ -553,6 +553,12 @@ func (tr *TaskRegistry) CreateSingletonTask(name string, interval time.Duration,
}
return nil, fmt.Errorf("singleton task already exists: %s (requested: %s)", taskName, name)
}
if strings.Contains(taskName, "memory-monitor") && strings.Contains(name, "memory-monitor") {
if tr.logger != nil {
tr.logger.Debug("Singleton enforcement: memory-monitor task %s already exists, rejecting %s", taskName, name)
}
return nil, fmt.Errorf("singleton memory-monitor task already exists: %s (requested: %s)", taskName, name)
}
}
// Check if exact task already exists
+50 -2
View File
@@ -304,8 +304,25 @@ func (mm *MemoryMonitor) LogMemoryStats(stats *MemoryStats) {
stats.SessionCount, stats.TaskCount, stats.CacheSize, stats.ConnectionPools)
}
// StartMonitoring starts continuous memory monitoring
// Global monitoring state
var (
globalMonitoringStarted bool
globalMonitoringMutex sync.Mutex
)
// StartMonitoring starts continuous memory monitoring as a global singleton
func (mm *MemoryMonitor) StartMonitoring(ctx context.Context, interval time.Duration) {
globalMonitoringMutex.Lock()
defer globalMonitoringMutex.Unlock()
// Check if monitoring is already started
if globalMonitoringStarted {
if !isTestMode() {
mm.logger.Debug("Memory monitoring already started, skipping duplicate start")
}
return
}
if interval <= 0 {
interval = 30 * time.Second
}
@@ -329,9 +346,12 @@ func (mm *MemoryMonitor) StartMonitoring(ctx context.Context, interval time.Dura
return
}
// Only start if task was newly created or we're sure it's not already running
task.Start()
globalMonitoringStarted = true
if !isTestMode() {
mm.logger.Info("Started memory monitoring with %v interval", interval)
mm.logger.Info("Started global memory monitoring with %v interval", interval)
}
}
@@ -386,6 +406,34 @@ func (mm *MemoryMonitor) GetMemoryPressure() MemoryPressureLevel {
return MemoryPressureNone
}
// StopMonitoring stops the global memory monitoring if it's running
func (mm *MemoryMonitor) StopMonitoring() {
globalMonitoringMutex.Lock()
defer globalMonitoringMutex.Unlock()
if !globalMonitoringStarted {
return
}
registry := GetGlobalTaskRegistry()
if task, exists := registry.GetTask("memory-monitor"); exists {
task.Stop()
globalMonitoringStarted = false
if !isTestMode() {
mm.logger.Info("Stopped global memory monitoring")
}
} else {
mm.logger.Errorf("Failed to find memory monitoring task to stop")
}
}
// IsMonitoringActive returns true if global memory monitoring is currently active
func (mm *MemoryMonitor) IsMonitoringActive() bool {
globalMonitoringMutex.Lock()
defer globalMonitoringMutex.Unlock()
return globalMonitoringStarted
}
// Global memory monitor instance
var (
globalMemoryMonitor *MemoryMonitor
+157
View File
@@ -0,0 +1,157 @@
package traefikoidc
import (
"context"
"runtime"
"sync"
"testing"
"time"
)
func TestMemoryMonitorSingleton(t *testing.T) {
// Reset global state
globalMonitoringMutex.Lock()
globalMonitoringStarted = false
globalMonitoringMutex.Unlock()
// Clear task registry for clean test
registry := GetGlobalTaskRegistry()
registry.StopAllTasks()
// Test that multiple StartMonitoring calls don't create multiple monitors
monitor := GetGlobalMemoryMonitor()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
goroutinesBefore := runtime.NumGoroutine()
// Call StartMonitoring multiple times (simulating multiple middleware instances)
var wg sync.WaitGroup
numCalls := 10
for i := 0; i < numCalls; i++ {
wg.Add(1)
go func() {
defer wg.Done()
monitor.StartMonitoring(ctx, 100*time.Millisecond)
}()
}
wg.Wait()
// Give some time for goroutines to start
time.Sleep(50 * time.Millisecond)
goroutinesAfter := runtime.NumGoroutine()
// Should only create one additional goroutine for the singleton task
goroutineDiff := goroutinesAfter - goroutinesBefore
if goroutineDiff > 2 { // Allow some tolerance for test runner goroutines
t.Errorf("Expected at most 2 additional goroutines, got %d", goroutineDiff)
}
// Check that monitoring is active
if !monitor.IsMonitoringActive() {
t.Error("Memory monitoring should be active")
}
// Verify only one task is registered
taskCount := registry.GetTaskCount()
if taskCount != 1 {
t.Errorf("Expected 1 task, got %d", taskCount)
}
// Check that the task is specifically memory-monitor
if task, exists := registry.GetTask("memory-monitor"); !exists {
t.Error("memory-monitor task should exist")
} else if task == nil {
t.Error("memory-monitor task should not be nil")
}
// Stop monitoring
monitor.StopMonitoring()
// Verify monitoring is no longer active
if monitor.IsMonitoringActive() {
t.Error("Memory monitoring should be inactive after stopping")
}
// Clean up
registry.StopAllTasks()
}
func TestMemoryMonitorSingletonTaskCreation(t *testing.T) {
// Reset global state
globalMonitoringMutex.Lock()
globalMonitoringStarted = false
globalMonitoringMutex.Unlock()
registry := GetGlobalTaskRegistry()
registry.StopAllTasks()
monitor1 := GetGlobalMemoryMonitor()
monitor2 := GetGlobalMemoryMonitor()
// Should be the same instance
if monitor1 != monitor2 {
t.Error("GetGlobalMemoryMonitor should return the same instance")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// First start should succeed
monitor1.StartMonitoring(ctx, 50*time.Millisecond)
if !monitor1.IsMonitoringActive() {
t.Error("First StartMonitoring should activate monitoring")
}
// Second start should be skipped
monitor2.StartMonitoring(ctx, 50*time.Millisecond)
// Should still only have one task
taskCount := registry.GetTaskCount()
if taskCount != 1 {
t.Errorf("Expected 1 task after duplicate start, got %d", taskCount)
}
// Clean up
monitor1.StopMonitoring()
registry.StopAllTasks()
}
func TestMemoryMonitorTaskRegistryEnforcement(t *testing.T) {
// Reset global state
globalMonitoringMutex.Lock()
globalMonitoringStarted = false
globalMonitoringMutex.Unlock()
registry := GetGlobalTaskRegistry()
registry.StopAllTasks()
// Try to create memory-monitor task directly through registry
task1, err1 := registry.CreateSingletonTask("memory-monitor", 50*time.Millisecond,
func() {}, GetSingletonNoOpLogger(), nil)
if err1 != nil {
t.Fatalf("First memory-monitor task creation should succeed: %v", err1)
}
// Try to create another memory-monitor task - should fail
task2, err2 := registry.CreateSingletonTask("memory-monitor-2", 50*time.Millisecond,
func() {}, GetSingletonNoOpLogger(), nil)
if err2 == nil {
t.Error("Second memory-monitor task creation should fail due to singleton enforcement")
if task2 != nil {
task2.Stop()
}
}
// Clean up
if task1 != nil {
task1.Stop()
}
registry.StopAllTasks()
}