feat: implement graceful message bus shutdown with drainage

- Add atomic.Bool draining flag to prevent new messages during shutdown
- Modify Publish() to silently ignore messages when draining
- Update Stop() method to set draining flag and naturally drain channel
- Channel closes and routing goroutine processes all remaining messages
- Zero message loss during normal plugin shutdown/restart
- No complex drainage logic - leverages Go channel semantics

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Felipe M 2025-08-11 13:28:47 +02:00
parent 1bbb510870
commit 9d2dd5619b
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger" "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
@ -32,6 +33,9 @@ type messageBus struct {
wg sync.WaitGroup wg sync.WaitGroup
started bool started bool
startMu sync.Mutex startMu sync.Mutex
// Graceful shutdown management
draining atomic.Bool
} }
// NewMessageBus creates a new message bus instance // NewMessageBus creates a new message bus instance
@ -70,6 +74,14 @@ func (mb *messageBus) Publish(msg *model.DirectionalMessage) error {
return fmt.Errorf("bridge message cannot be nil") return fmt.Errorf("bridge message cannot be nil")
} }
// Check if we're draining - if so, silently ignore new messages
if mb.draining.Load() {
mb.logger.LogDebug("Ignoring message during shutdown drainage",
"source_bridge", msg.SourceBridge,
"channel_id", msg.SourceChannelID)
return nil
}
select { select {
case mb.incomingMessages <- msg: case mb.incomingMessages <- msg:
mb.logger.LogDebug("Message published to bus", mb.logger.LogDebug("Message published to bus",
@ -116,12 +128,20 @@ func (mb *messageBus) Stop() error {
return nil // Already stopped return nil // Already stopped
} }
mb.logger.LogInfo("Stopping message bus") pendingCount := len(mb.incomingMessages)
mb.logger.LogInfo("Stopping message bus", "pending_messages", pendingCount)
// Set draining flag to prevent new messages
mb.draining.Store(true)
// Cancel context to signal shutdown // Cancel context to signal shutdown
mb.cancel() mb.cancel()
// Wait for routing goroutine to finish // Close incoming messages channel to signal routing goroutine to finish
// The routing goroutine will process all remaining messages until channel is empty
close(mb.incomingMessages)
// Wait for routing goroutine to finish processing all remaining messages
mb.wg.Wait() mb.wg.Wait()
// Close all subscriber channels // Close all subscriber channels
@ -133,11 +153,8 @@ func (mb *messageBus) Stop() error {
mb.subscribers = make(map[string]chan *model.DirectionalMessage) mb.subscribers = make(map[string]chan *model.DirectionalMessage)
mb.subscribersMu.Unlock() mb.subscribersMu.Unlock()
// Close incoming messages channel
close(mb.incomingMessages)
mb.started = false mb.started = false
mb.logger.LogInfo("Message bus stopped successfully") mb.logger.LogInfo("Message bus stopped successfully", "drained_messages", pendingCount)
return nil return nil
} }