Documentation Index
Fetch the complete documentation index at: https://mintlify.com/minekube/gate/llms.txt
Use this file to discover all available pages before exploring further.
Lifecycle events allow you to hook into the proxy’s startup and shutdown phases, enabling proper initialization and cleanup of your plugin resources.
Proxy Lifecycle
Gate’s proxy goes through several distinct phases during its lifetime:
- Initialization: Proxy is created with configuration
- Ready: Proxy starts listening for connections
- Running: Proxy handles player connections and server communication
- Pre-Shutdown: Proxy stops accepting new connections
- Shutdown: Proxy disconnects all players and cleans up resources
ReadyEvent
The ReadyEvent is fired once the proxy has successfully initialized and is ready to serve connections. This is the ideal place to perform post-initialization setup.
Event Structure
type ReadyEvent struct {
addr string // The address the proxy is listening on
}
// Addr returns the address the proxy is listening on
func (r *ReadyEvent) Addr() string
When It’s Fired
- After the proxy successfully binds to its listening address
- Before any player connections are accepted
- May be triggered multiple times on config reloads
Example Usage
import (
"github.com/robinbraemer/event"
"go.minekube.com/gate/pkg/edition/java/proxy"
)
func setupLifecycle(p *proxy.Proxy) {
mgr := p.Event()
event.Subscribe(mgr, 0, func(e *proxy.ReadyEvent) {
log.Info("Proxy is ready!", "address", e.Addr())
// Initialize plugin resources
initializeDatabase()
startBackgroundTasks()
loadPluginData()
// Register dynamic servers
registerBackendServers(p)
})
}
Use Cases
- Database Initialization: Connect to databases or external services
- Dynamic Server Registration: Register backend servers programmatically
- Background Tasks: Start periodic tasks or workers
- Plugin Communication: Notify other systems that the proxy is online
- Metrics Collection: Initialize monitoring and metrics systems
PreShutdownEvent
The PreShutdownEvent is fired after the proxy has stopped accepting new connections but before any players are disconnected. This is your last opportunity to interact with connected players.
Event Structure
type PreShutdownEvent struct {
reason component.Component // may be nil
}
// Reason returns the shutdown reason used to disconnect players with
// May be nil!
func (s *PreShutdownEvent) Reason() component.Component
// SetReason sets the shutdown reason used to disconnect players with
func (s *PreShutdownEvent) SetReason(reason component.Component)
When It’s Fired
- After the proxy stops accepting new connections
- Before any players are disconnected
- The proxy will wait for all event listeners to complete
Example Usage
event.Subscribe(mgr, 0, func(e *proxy.PreShutdownEvent) {
log.Info("Proxy is shutting down")
// Customize the disconnect reason
e.SetReason(&component.Text{
Content: "Server is restarting. Please reconnect in a moment.",
S: component.Style{
Color: component.Yellow,
},
})
// Transfer players to another proxy
transferPlayersToFallbackProxy()
// Save player data
saveAllPlayerData()
// Notify players of shutdown
notifyConnectedPlayers("Server shutting down in 5 seconds...")
})
Use Cases
- Player Transfer: Transfer players to another proxy instance
- Data Persistence: Save player data before disconnection
- Custom Disconnect Messages: Provide informative shutdown reasons
- Graceful Degradation: Notify external services of the shutdown
- Cleanup Preparation: Prepare for final cleanup operations
ShutdownEvent
The ShutdownEvent is fired after the proxy has stopped accepting connections and after PreShutdownEvent, but before the proxy process exits.
Event Structure
type ShutdownEvent struct{}
When It’s Fired
- After all players have been disconnected
- Before the proxy process terminates
- After
PreShutdownEvent has been processed
Example Usage
event.Subscribe(mgr, 0, func(e *proxy.ShutdownEvent) {
log.Info("Cleaning up proxy resources")
// Stop background tasks
stopBackgroundWorkers()
// Close external connections
disconnectFromDatabase()
closeAPIConnections()
// Save final state
saveProxyState()
// Release resources
releaseFileHandles()
clearCaches()
})
Use Cases
- Resource Cleanup: Close connections, file handles, and other resources
- Background Task Termination: Stop goroutines and worker pools
- Final Data Persistence: Save any remaining state to disk
- External Service Notification: Notify monitoring systems of shutdown
- Graceful Shutdown: Ensure proper cleanup of plugin dependencies
Complete Lifecycle Example
Here’s a comprehensive example showing how to use all lifecycle events together:
package main
import (
"context"
"log"
"sync"
"time"
"github.com/robinbraemer/event"
"go.minekube.com/common/minecraft/component"
"go.minekube.com/gate/pkg/edition/java/proxy"
)
type MyPlugin struct {
proxy *proxy.Proxy
db *Database
workers *WorkerPool
shutdown chan struct{}
wg sync.WaitGroup
}
func NewPlugin(p *proxy.Proxy) *MyPlugin {
plugin := &MyPlugin{
proxy: p,
shutdown: make(chan struct{}),
}
plugin.registerLifecycleEvents()
return plugin
}
func (p *MyPlugin) registerLifecycleEvents() {
mgr := p.proxy.Event()
// Handle proxy ready
event.Subscribe(mgr, 0, func(e *proxy.ReadyEvent) {
log.Printf("Proxy ready on %s", e.Addr())
// Initialize database
var err error
p.db, err = ConnectDatabase("postgres://...")
if err != nil {
log.Printf("Failed to connect to database: %v", err)
return
}
log.Println("Database connected")
// Start background workers
p.workers = NewWorkerPool(5)
p.workers.Start()
// Start periodic tasks
p.wg.Add(1)
go p.runPeriodicTasks()
log.Println("Plugin fully initialized")
})
// Handle pre-shutdown
event.Subscribe(mgr, 0, func(e *proxy.PreShutdownEvent) {
log.Println("Pre-shutdown initiated")
// Set a friendly disconnect message
e.SetReason(&component.Text{
Content: "Server is restarting\nPlease reconnect in 30 seconds",
S: component.Style{
Color: component.Gold,
},
})
// Save all player data
if err := p.saveAllPlayers(); err != nil {
log.Printf("Error saving player data: %v", err)
}
log.Println("Pre-shutdown complete")
})
// Handle shutdown
event.Subscribe(mgr, 0, func(e *proxy.ShutdownEvent) {
log.Println("Shutdown initiated")
// Signal shutdown to background tasks
close(p.shutdown)
// Wait for background tasks to complete (with timeout)
done := make(chan struct{})
go func() {
p.wg.Wait()
close(done)
}()
select {
case <-done:
log.Println("Background tasks completed")
case <-time.After(10 * time.Second):
log.Println("Background tasks timed out")
}
// Stop workers
if p.workers != nil {
p.workers.Stop()
}
// Close database
if p.db != nil {
if err := p.db.Close(); err != nil {
log.Printf("Error closing database: %v", err)
} else {
log.Println("Database closed")
}
}
log.Println("Shutdown complete")
})
}
func (p *MyPlugin) runPeriodicTasks() {
defer p.wg.Done()
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// Perform periodic task
p.cleanupOldData()
case <-p.shutdown:
log.Println("Stopping periodic tasks")
return
}
}
}
func (p *MyPlugin) saveAllPlayers() error {
for _, player := range p.proxy.Players() {
if err := p.db.SavePlayer(player); err != nil {
return err
}
}
return nil
}
func (p *MyPlugin) cleanupOldData() {
// Periodic cleanup logic
}
Best Practices
1. Handle Initialization Failures
event.Subscribe(mgr, 0, func(e *proxy.ReadyEvent) {
if err := initializePlugin(); err != nil {
log.Error(err, "Failed to initialize plugin")
// Decide whether to continue or shutdown
return
}
})
2. Use Timeouts for Cleanup
event.Subscribe(mgr, 0, func(e *proxy.ShutdownEvent) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := cleanupWithContext(ctx); err != nil {
log.Error(err, "Cleanup failed or timed out")
}
})
3. Handle Multiple Ready Events
Since ReadyEvent may fire multiple times during config reloads, ensure your initialization is idempotent:
var initialized bool
event.Subscribe(mgr, 0, func(e *proxy.ReadyEvent) {
if initialized {
// Handle reload
reloadConfiguration()
return
}
// First-time initialization
initialize()
initialized = true
})
4. Log Lifecycle Events
Always log lifecycle events for debugging and monitoring:
event.Subscribe(mgr, 0, func(e *proxy.ReadyEvent) {
log.Info("Proxy ready", "address", e.Addr())
})
event.Subscribe(mgr, 0, func(e *proxy.PreShutdownEvent) {
log.Info("Proxy shutting down", "reason", e.Reason())
})
event.Subscribe(mgr, 0, func(e *proxy.ShutdownEvent) {
log.Info("Proxy shutdown complete")
})
See Also