mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
cedee416a8
* General improvements and bug fixes. * Improve tests coverage. * fixup! Improve tests coverage. * Update README.md with latest changes. * Fix the uint32 * Resolve issue with race condition for logging. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * Fix the test of the rate limiter * Add default ratelimit.json file * Update dependencies. * Significant refactor. * fixup! Significant refactor. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025
195 lines
4.5 KiB
Go
195 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
|
)
|
|
|
|
// ShutdownManager manages graceful shutdown for all components
|
|
type ShutdownManager struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
components []ShutdownComponent
|
|
wg sync.WaitGroup
|
|
shutdownOnce sync.Once
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// ShutdownComponent represents a component that needs graceful shutdown
|
|
type ShutdownComponent struct {
|
|
Shutdown func(context.Context) error
|
|
Name string
|
|
}
|
|
|
|
// NewShutdownManager creates a new shutdown manager
|
|
func NewShutdownManager(ctx context.Context) *ShutdownManager {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
return &ShutdownManager{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// RegisterComponent registers a component for graceful shutdown
|
|
func (sm *ShutdownManager) RegisterComponent(name string, shutdown func(context.Context) error) {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
sm.components = append(sm.components, ShutdownComponent{
|
|
Name: name,
|
|
Shutdown: shutdown,
|
|
})
|
|
}
|
|
|
|
// RunGoroutine starts a goroutine that respects the shutdown context
|
|
func (sm *ShutdownManager) RunGoroutine(name string, fn func(context.Context)) {
|
|
sm.wg.Add(1)
|
|
go func() {
|
|
defer sm.wg.Done()
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Debug(&libpack_logging.LogMessage{
|
|
Message: "Starting managed goroutine",
|
|
Pairs: map[string]interface{}{"name": name},
|
|
})
|
|
}
|
|
fn(sm.ctx)
|
|
cfgMutex.RLock()
|
|
logger = cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Debug(&libpack_logging.LogMessage{
|
|
Message: "Managed goroutine finished",
|
|
Pairs: map[string]interface{}{"name": name},
|
|
})
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Shutdown initiates graceful shutdown of all components
|
|
func (sm *ShutdownManager) Shutdown(timeout time.Duration) error {
|
|
var err error
|
|
sm.shutdownOnce.Do(func() {
|
|
err = sm.doShutdown(timeout)
|
|
})
|
|
return err
|
|
}
|
|
|
|
// doShutdown performs the actual shutdown logic
|
|
func (sm *ShutdownManager) doShutdown(timeout time.Duration) error {
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Info(&libpack_logging.LogMessage{
|
|
Message: "Initiating graceful shutdown",
|
|
})
|
|
}
|
|
|
|
// Cancel the context to signal all goroutines to stop
|
|
sm.cancel()
|
|
|
|
// Create a timeout context for component shutdown
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), timeout)
|
|
defer shutdownCancel()
|
|
|
|
// Shutdown all registered components
|
|
sm.mu.Lock()
|
|
components := make([]ShutdownComponent, len(sm.components))
|
|
copy(components, sm.components)
|
|
sm.mu.Unlock()
|
|
|
|
var shutdownWg sync.WaitGroup
|
|
for _, comp := range components {
|
|
shutdownWg.Add(1)
|
|
go func(c ShutdownComponent) {
|
|
defer shutdownWg.Done()
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Info(&libpack_logging.LogMessage{
|
|
Message: "Shutting down component",
|
|
Pairs: map[string]interface{}{"component": c.Name},
|
|
})
|
|
}
|
|
if err := c.Shutdown(shutdownCtx); err != nil {
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Error(&libpack_logging.LogMessage{
|
|
Message: "Error shutting down component",
|
|
Pairs: map[string]interface{}{
|
|
"component": c.Name,
|
|
"error": err.Error(),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}(comp)
|
|
}
|
|
|
|
// Wait for all components to shutdown
|
|
componentsDone := make(chan struct{})
|
|
go func() {
|
|
shutdownWg.Wait()
|
|
close(componentsDone)
|
|
}()
|
|
|
|
// Wait for goroutines with timeout
|
|
goroutinesDone := make(chan struct{})
|
|
go func() {
|
|
sm.wg.Wait()
|
|
close(goroutinesDone)
|
|
}()
|
|
|
|
select {
|
|
case <-componentsDone:
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Info(&libpack_logging.LogMessage{
|
|
Message: "All components shut down successfully",
|
|
})
|
|
}
|
|
case <-shutdownCtx.Done():
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Warning(&libpack_logging.LogMessage{
|
|
Message: "Component shutdown timed out",
|
|
})
|
|
}
|
|
}
|
|
|
|
select {
|
|
case <-goroutinesDone:
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Info(&libpack_logging.LogMessage{
|
|
Message: "All goroutines finished",
|
|
})
|
|
}
|
|
case <-time.After(timeout):
|
|
cfgMutex.RLock()
|
|
logger := cfg.Logger
|
|
cfgMutex.RUnlock()
|
|
if logger != nil {
|
|
logger.Warning(&libpack_logging.LogMessage{
|
|
Message: "Some goroutines didn't finish within timeout",
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|