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:
parent
1bbb510870
commit
9d2dd5619b
1 changed files with 23 additions and 6 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue