Skip to content

Instantly share code, notes, and snippets.

@katallaxie
Last active May 7, 2023 04:56
Show Gist options
  • Save katallaxie/2c5624c70cda289aa9f7bed7de126729 to your computer and use it in GitHub Desktop.
Save katallaxie/2c5624c70cda289aa9f7bed7de126729 to your computer and use it in GitHub Desktop.
Server in Go with multiple listeners and signals.
package server
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"golang.org/x/sync/errgroup"
)
// Server is the interface to the server
type Server interface {
// Run is running a new routine
Listen(listener Listener)
// Waits for the server to fail,
// or gracefully shutdown
Wait() error
}
// Opt ...
type Opt func(*Opts)
// Opts ...
type Opts struct {
// ReloadSignal
ReloadSignal syscall.Signal
// TermSignal
TermSignal syscall.Signal
// KillSignal
KillSignal syscall.Signal
}
type listeners map[Listener]bool
// server holds the instance info of the server
type server struct {
errGroup *errgroup.Group
errCtx context.Context
cancel context.CancelFunc
listeners map[Listener]bool
sys chan os.Signal
opts *Opts
}
// NewServer ....
func NewServer(ctx context.Context, opts ...Opt) Server {
options := &Opts{}
s := new(server)
s.opts = options
ctx, cancel := context.WithCancel(ctx)
s.cancel = cancel
s.errGroup, s.errCtx = errgroup.WithContext(ctx)
s.listeners = make(listeners)
configure(s, opts...)
configureSignals(s)
return s
}
// Listen ...
func (s *server) Listen(listener Listener) {
if _, found := s.listeners[listener]; found {
return
}
s.listeners[listener] = true
g := s.errGroup
g.Go(listener.Start())
}
// Wait ...
func (s *server) Wait() error {
// create ticker for interrupt signals
ticker := time.NewTicker(1 * time.Second)
ctx := s.errCtx
for {
select {
case <-ticker.C:
case <-s.sys:
s.cancel()
case <-ctx.Done():
for listener := range s.listeners {
listener.Stop()
}
if err := ctx.Err(); err != nil {
return err
}
return nil
}
}
}
// Listener is the interface to a listener,
// so starting and shutdown of a listener,
// or any routine.
type Listener interface {
Start() func() error
Stop() error
}
func configureSignals(s *server) {
s.sys = make(chan os.Signal, 1)
signal.Notify(s.sys, s.opts.ReloadSignal, s.opts.KillSignal, s.opts.TermSignal)
}
func configure(s *server, opts ...Opt) error {
for _, o := range opts {
o(s.opts)
}
if s.opts.Env == "" {
s.opts.Env = env.Env
}
s.opts.TermSignal = syscall.SIGTERM
s.opts.ReloadSignal = syscall.SIGHUP
s.opts.KillSignal = syscall.SIGINT
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment