Wait for IRC listener cleanup and HTTP shutdown on stop (closes #159)
check / check (push) Waiting to run
check / check (push) Waiting to run
Stopping the server returned before the IRC listener's connections had run their cleanup, and the database closed under them, so their sessions, clients and channel memberships stayed and their nicks stayed taken after a restart. The IRC listener's stop now waits up to five seconds for every connection's cleanup, which now runs on a context that stopping does not cancel. The HTTP server's stop hook now runs Shutdown itself and waits for it; httpServer is set before serving starts, and the server's own signal watcher, which raced fx's stop hooks, is gone. Model: opus-5-5
This commit was merged in pull request #166.
This commit is contained in:
@@ -16,6 +16,10 @@ import (
|
||||
"sneak.berlin/go/neoirc/internal/service"
|
||||
)
|
||||
|
||||
// stopTimeout is the longest stop waits for the connections'
|
||||
// cleanup, which ends their sessions.
|
||||
const stopTimeout = 5 * time.Second
|
||||
|
||||
// Params defines the dependencies for creating an IRC
|
||||
// Server.
|
||||
type Params struct {
|
||||
@@ -40,6 +44,10 @@ type Server struct {
|
||||
conns map[*Conn]struct{}
|
||||
cancel context.CancelFunc
|
||||
|
||||
// serving counts the connections whose serve, cleanup
|
||||
// included, has not returned yet.
|
||||
serving sync.WaitGroup
|
||||
|
||||
// pingInterval and pongDeadline are the constants of the
|
||||
// same names, except in tests, which shorten them.
|
||||
pingInterval time.Duration
|
||||
@@ -63,6 +71,7 @@ func New(
|
||||
listener: nil,
|
||||
cancel: nil,
|
||||
mu: sync.Mutex{},
|
||||
serving: sync.WaitGroup{},
|
||||
|
||||
pingInterval: pingInterval,
|
||||
pongDeadline: pongDeadline,
|
||||
@@ -112,7 +121,10 @@ func (s *Server) start(ctx context.Context, addr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stop shuts down the listener and all connections.
|
||||
// stop shuts down the listener and all connections, then
|
||||
// waits up to stopTimeout for every connection's cleanup to
|
||||
// finish, so that their sessions are ended before the
|
||||
// database closes.
|
||||
func (s *Server) stop() {
|
||||
if s.cancel != nil {
|
||||
s.cancel()
|
||||
@@ -127,6 +139,22 @@ func (s *Server) stop() {
|
||||
c.conn.Close() //nolint:errcheck,gosec
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
s.serving.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(stopTimeout):
|
||||
s.log.Error(
|
||||
"irc connections not cleaned up before stop timeout",
|
||||
"timeout", stopTimeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// acceptLoop accepts new connections.
|
||||
@@ -152,10 +180,24 @@ func (s *Server) acceptLoop(ctx context.Context) {
|
||||
)
|
||||
|
||||
s.mu.Lock()
|
||||
|
||||
// stop cancels ctx before it takes s.mu to close the
|
||||
// connections, so a connection accepted while stop
|
||||
// runs is either closed and waited for by stop or
|
||||
// closed here.
|
||||
if ctx.Err() != nil {
|
||||
s.mu.Unlock()
|
||||
tcpConn.Close() //nolint:errcheck,gosec
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
s.conns[client] = struct{}{}
|
||||
s.serving.Add(1)
|
||||
s.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer s.serving.Done()
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
delete(s.conns, client)
|
||||
|
||||
Reference in New Issue
Block a user