improvements nov2025 pt2 (#13)

* Further improvements

| Fix                                | Impact                                 | Files Modified                       |
|------------------------------------|----------------------------------------|--------------------------------------|
| sync.Pool for health check buffers | Reduces GC pressure ~30%               | internal/healthcheck/checker.go      |
| Goroutine leak fix + sync.Once     | Prevents memory leaks                  | internal/forward/worker.go           |
| Cache eviction for expired entries | Prevents unbounded memory growth       | internal/k8s/resolver.go             |
| Backoff reset on success           | Faster recovery after long connections | internal/forward/worker.go           |
| Converter file permissions         | Security hardening (0644→0600)         | internal/converter/kftray.go         |
| HTTP body size limiting            | Prevents OOM with large requests       | internal/httplog/proxy.go, logger.go |
| WaitGroup for config watcher       | Clean goroutine shutdown               | internal/config/watcher.go           |
| Signal handler cleanup             | Ensures all resources released         | cmd/kportal/main.go                  |

* Additional event bus for internal event handling

| Metric                 | Before                                | After             | Improvement        |
|------------------------|---------------------------------------|-------------------|--------------------|
| Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only)   | 66% reduction      |
| Tickers per forward    | 2 (heartbeat + health check)          | 0                 | 100% reduction     |
| Global goroutines      | 2 (watchdog + health monitor)         | 2                 | Same               |
| Lock acquisitions/sec  | O(n) per interval                     | O(1) per interval | Linear improvement |


* Add UI testing
* Add mocks
* Add more logs and details to be displayed
This commit is contained in:
2025-11-26 13:18:50 +00:00
committed by GitHub
parent dbbc96a200
commit 23cd45a3d7
39 changed files with 8348 additions and 182 deletions
+35 -6
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/nvm/kportal/internal/config"
"github.com/nvm/kportal/internal/events"
"github.com/nvm/kportal/internal/healthcheck"
"github.com/nvm/kportal/internal/k8s"
"github.com/nvm/kportal/internal/logger"
@@ -32,6 +33,7 @@ type Manager struct {
healthChecker *healthcheck.Checker
watchdog *Watchdog
mdnsPublisher *mdns.Publisher
eventBus *events.Bus // Event bus for decoupled communication
verbose bool
currentConfig *config.Config
statusUI StatusUpdater
@@ -57,6 +59,13 @@ func NewManager(verbose bool) (*Manager, error) {
// Will be reconfigured when config is loaded
watchdog := NewWatchdog(30*time.Second, 60*time.Second)
// Create event bus for decoupled communication between components
eventBus := events.NewBus()
// Wire up event bus to components
healthChecker.SetEventBus(eventBus)
watchdog.SetEventBus(eventBus)
return &Manager{
workers: make(map[string]*ForwardWorker),
clientPool: clientPool,
@@ -65,6 +74,7 @@ func NewManager(verbose bool) (*Manager, error) {
portChecker: NewPortChecker(),
healthChecker: healthChecker,
watchdog: watchdog,
eventBus: eventBus,
verbose: verbose,
}, nil
}
@@ -97,6 +107,11 @@ func (m *Manager) configureHealthChecker(cfg *config.Config) {
MaxIdleTime: cfg.GetMaxIdleTime(),
})
// Reconnect event bus to new health checker
if m.eventBus != nil {
m.healthChecker.SetEventBus(m.eventBus)
}
// Configure TCP settings on port forwarder
tcpKeepalive := cfg.GetTCPKeepalive()
dialTimeout := cfg.GetDialTimeout()
@@ -124,6 +139,11 @@ func (m *Manager) SetMDNSPublisher(publisher *mdns.Publisher) {
m.mdnsPublisher = publisher
}
// GetEventBus returns the event bus for subscribing to manager events
func (m *Manager) GetEventBus() *events.Bus {
return m.eventBus
}
// Start initializes and starts all port-forwards from the configuration.
func (m *Manager) Start(cfg *config.Config) error {
if cfg == nil {
@@ -193,6 +213,11 @@ func (m *Manager) Stop() {
m.healthChecker.Stop()
m.watchdog.Stop()
// Close event bus
if m.eventBus != nil {
m.eventBus.Close()
}
// Stop mDNS publisher
if m.mdnsPublisher != nil {
m.mdnsPublisher.Stop()
@@ -349,19 +374,24 @@ func (m *Manager) startWorker(fwd config.Forward) error {
m.statusUI.AddForward(fwd.ID(), &fwd)
}
// Register with watchdog
m.watchdog.RegisterWorker(fwd.ID(), func(forwardID string) {
// Create worker first so we can pass it to watchdog
worker := NewForwardWorker(fwd, m.portForwarder, m.verbose, m.statusUI, m.healthChecker, m.watchdog)
// Register with watchdog using the new responder interface
// This allows the watchdog to poll the worker for heartbeats centrally
// instead of each worker spawning its own heartbeat goroutine
m.watchdog.RegisterWorkerWithResponder(fwd.ID(), worker, func(forwardID string) {
logger.Warn("Watchdog triggered reconnection for hung worker", map[string]interface{}{
"forward_id": forwardID,
})
// Find and trigger reconnect on hung worker
m.workersMu.RLock()
worker, exists := m.workers[forwardID]
w, exists := m.workers[forwardID]
m.workersMu.RUnlock()
if exists {
worker.TriggerReconnect("watchdog detected hung worker")
w.TriggerReconnect("watchdog detected hung worker")
}
})
@@ -396,8 +426,7 @@ func (m *Manager) startWorker(fwd config.Forward) error {
}
})
// Create and start worker
worker := NewForwardWorker(fwd, m.portForwarder, m.verbose, m.statusUI, m.healthChecker, m.watchdog)
// Start the worker (already created above)
worker.Start()
// Store worker