mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
Dependency injection improvements (#288)
Refactor server life-cycle into it's own file, make service startup and monitoring more consistent and testable. * Extract services creation in preparation for DI * pop3: rename New to NewServer * lifecycle: Add fatal error Notify() * web: Introduce Server struct w/ Notify() * Extract Start in lifecycle * Add Start() to Hub * RetentionScanner startup consistent with other svcs * Remove global shutdown channel * Implement a readiness notification system
This commit is contained in:
@@ -13,47 +13,53 @@ import (
|
||||
|
||||
// Server defines an instance of the POP3 server.
|
||||
type Server struct {
|
||||
config config.POP3 // POP3 configuration.
|
||||
store storage.Store // Mail store.
|
||||
listener net.Listener // TCP listener.
|
||||
globalShutdown chan bool // Inbucket shutdown signal.
|
||||
wg *sync.WaitGroup // Waitgroup tracking sessions.
|
||||
config config.POP3 // POP3 configuration.
|
||||
store storage.Store // Mail store.
|
||||
listener net.Listener // TCP listener.
|
||||
wg *sync.WaitGroup // Waitgroup tracking sessions.
|
||||
notify chan error // Notify on fatal error.
|
||||
}
|
||||
|
||||
// New creates a new Server struct.
|
||||
func New(pop3Config config.POP3, shutdownChan chan bool, store storage.Store) *Server {
|
||||
// NewServer creates a new, unstarted, POP3 server.
|
||||
func NewServer(pop3Config config.POP3, store storage.Store) *Server {
|
||||
return &Server{
|
||||
config: pop3Config,
|
||||
store: store,
|
||||
globalShutdown: shutdownChan,
|
||||
wg: new(sync.WaitGroup),
|
||||
config: pop3Config,
|
||||
store: store,
|
||||
wg: new(sync.WaitGroup),
|
||||
notify: make(chan error, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server and listen for connections
|
||||
func (s *Server) Start(ctx context.Context) {
|
||||
func (s *Server) Start(ctx context.Context, readyFunc func()) {
|
||||
slog := log.With().Str("module", "pop3").Str("phase", "startup").Logger()
|
||||
addr, err := net.ResolveTCPAddr("tcp4", s.config.Addr)
|
||||
if err != nil {
|
||||
slog.Error().Err(err).Msg("Failed to build tcp4 address")
|
||||
s.emergencyShutdown()
|
||||
s.notify <- err
|
||||
close(s.notify)
|
||||
return
|
||||
}
|
||||
slog.Info().Str("addr", addr.String()).Msg("POP3 listening on tcp4")
|
||||
s.listener, err = net.ListenTCP("tcp4", addr)
|
||||
if err != nil {
|
||||
slog.Error().Err(err).Msg("Failed to start tcp4 listener")
|
||||
s.emergencyShutdown()
|
||||
s.notify <- err
|
||||
close(s.notify)
|
||||
return
|
||||
}
|
||||
// Listener go routine.
|
||||
|
||||
// Start listener go routine.
|
||||
go s.serve(ctx)
|
||||
readyFunc()
|
||||
|
||||
// Wait for shutdown.
|
||||
select {
|
||||
case _ = <-ctx.Done():
|
||||
}
|
||||
slog = log.With().Str("module", "pop3").Str("phase", "shutdown").Logger()
|
||||
slog.Debug().Msg("POP3 shutdown requested, connections will be drained")
|
||||
|
||||
// Closing the listener will cause the serve() go routine to exit.
|
||||
if err := s.listener.Close(); err != nil {
|
||||
slog.Error().Err(err).Msg("Failed to close POP3 listener")
|
||||
@@ -88,7 +94,8 @@ func (s *Server) serve(ctx context.Context) {
|
||||
return
|
||||
default:
|
||||
// Something went wrong.
|
||||
s.emergencyShutdown()
|
||||
s.notify <- err
|
||||
close(s.notify)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -100,18 +107,14 @@ func (s *Server) serve(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) emergencyShutdown() {
|
||||
// Shutdown Inbucket
|
||||
select {
|
||||
case _ = <-s.globalShutdown:
|
||||
default:
|
||||
close(s.globalShutdown)
|
||||
}
|
||||
}
|
||||
|
||||
// Drain causes the caller to block until all active POP3 sessions have finished
|
||||
func (s *Server) Drain() {
|
||||
// Wait for sessions to close
|
||||
s.wg.Wait()
|
||||
log.Debug().Str("module", "pop3").Str("phase", "shutdown").Msg("POP3 connections have drained")
|
||||
}
|
||||
|
||||
// Notify allows the running POP3 server to be monitored for a fatal error.
|
||||
func (s *Server) Notify() <-chan error {
|
||||
return s.notify
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user