Files
kportal/internal/events/bus.go
T
lukaszraczylo 23cd45a3d7 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
2025-11-26 13:18:50 +00:00

180 lines
4.0 KiB
Go

package events
import (
"sync"
)
// EventType represents the type of event
type EventType string
const (
// Forward lifecycle events
EventForwardStarting EventType = "forward.starting"
EventForwardConnected EventType = "forward.connected"
EventForwardDisconnected EventType = "forward.disconnected"
EventForwardReconnecting EventType = "forward.reconnecting"
EventForwardStopped EventType = "forward.stopped"
EventForwardError EventType = "forward.error"
// Health events
EventHealthStatusChanged EventType = "health.status_changed"
EventHealthStale EventType = "health.stale"
// Watchdog events
EventWorkerHung EventType = "watchdog.worker_hung"
// Config events
EventConfigReloaded EventType = "config.reloaded"
)
// Event represents a system event
type Event struct {
Type EventType
ForwardID string
Data map[string]interface{}
}
// Handler is a function that handles events
type Handler func(event Event)
// Bus is a simple event bus for decoupled communication between components
type Bus struct {
mu sync.RWMutex
handlers map[EventType][]Handler
closed bool
}
// NewBus creates a new event bus
func NewBus() *Bus {
return &Bus{
handlers: make(map[EventType][]Handler),
}
}
// Subscribe registers a handler for a specific event type
func (b *Bus) Subscribe(eventType EventType, handler Handler) {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
b.handlers[eventType] = append(b.handlers[eventType], handler)
}
// SubscribeAll registers a handler for all events
func (b *Bus) SubscribeAll(handler Handler) {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return
}
// Subscribe to all known event types
eventTypes := []EventType{
EventForwardStarting,
EventForwardConnected,
EventForwardDisconnected,
EventForwardReconnecting,
EventForwardStopped,
EventForwardError,
EventHealthStatusChanged,
EventHealthStale,
EventWorkerHung,
EventConfigReloaded,
}
for _, et := range eventTypes {
b.handlers[et] = append(b.handlers[et], handler)
}
}
// Publish sends an event to all registered handlers
// Handlers are called synchronously in the order they were registered
func (b *Bus) Publish(event Event) {
b.mu.RLock()
if b.closed {
b.mu.RUnlock()
return
}
handlers := make([]Handler, len(b.handlers[event.Type]))
copy(handlers, b.handlers[event.Type])
b.mu.RUnlock()
for _, handler := range handlers {
handler(event)
}
}
// PublishAsync sends an event to all registered handlers asynchronously
func (b *Bus) PublishAsync(event Event) {
b.mu.RLock()
if b.closed {
b.mu.RUnlock()
return
}
handlers := make([]Handler, len(b.handlers[event.Type]))
copy(handlers, b.handlers[event.Type])
b.mu.RUnlock()
for _, handler := range handlers {
go handler(event)
}
}
// Close stops the event bus and prevents new subscriptions/publications
func (b *Bus) Close() {
b.mu.Lock()
defer b.mu.Unlock()
b.closed = true
b.handlers = make(map[EventType][]Handler)
}
// Helper functions for creating common events
// NewForwardEvent creates a forward-related event
func NewForwardEvent(eventType EventType, forwardID string, data map[string]interface{}) Event {
return Event{
Type: eventType,
ForwardID: forwardID,
Data: data,
}
}
// NewHealthEvent creates a health status change event
func NewHealthEvent(forwardID string, status string, errorMsg string) Event {
return Event{
Type: EventHealthStatusChanged,
ForwardID: forwardID,
Data: map[string]interface{}{
"status": status,
"error_msg": errorMsg,
},
}
}
// NewStaleEvent creates a stale connection event
func NewStaleEvent(forwardID string, reason string) Event {
return Event{
Type: EventHealthStale,
ForwardID: forwardID,
Data: map[string]interface{}{
"reason": reason,
},
}
}
// NewWorkerHungEvent creates a hung worker event
func NewWorkerHungEvent(forwardID string, timeSinceHeartbeat string) Event {
return Event{
Type: EventWorkerHung,
ForwardID: forwardID,
Data: map[string]interface{}{
"time_since_heartbeat": timeSinceHeartbeat,
},
}
}