lolibrary/lolibrary

View on GitHub
foundation/foundation.go

Summary

Maintainability
A
0 mins
Test Coverage
package foundation

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "runtime"
    "sync"
    "syscall"
    "time"

    "github.com/lolibrary/lolibrary/libraries/filters"
    "github.com/monzo/slog"
    "github.com/monzo/typhon"
)

type Server struct {
    version string
    name    string
    id      string

    srv *typhon.Server
}

func init() {
    typhon.Client = typhon.Client.Filter(filters.ClientFilters)
}

var runOnce sync.Once

// Init sets up and returns a server we can register a service with.
func Init(name string) *Server {
    srv := NewServer(name)
    runOnce.Do(func() {
        // set up logging
        // logging.Init(name)
    })

    return srv
}

func NewServer(name string) *Server {
    srv := &Server{
        version: Version,
        name:    name,
    }

    id, err := os.Hostname()
    if err != nil {
        panic(err)
    }

    srv.id = id

    return srv
}

// ID returns the worker ID of this service, derived from hostname.
func (s *Server) ID() string {
    return s.id
}

// Name returns the name of this service, e.g. service.foo
func (s *Server) Name() string {
    return s.name
}

// Version returns the version of this service, set at compile time from git.
func (s *Server) Version() string {
    return s.version
}

// Run runs a service with typhon, handling shutdown gracefully.
func (s *Server) Run(svc typhon.Service) {
    shutdown := make(chan os.Signal, 2)
    signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)

    started := make(chan error)
    go func() {
        started <- s.Start(svc)
    }()

    select {
    case err := <-started:
        if err != nil {
            panic(err)
        }
    case s := <-shutdown:
        slog.Info(nil, "⚠️  Got a shutdown signal (%v) before service started, exiting", s)
        os.Exit(0)
    case <-time.After(time.Minute * 5):
        slog.Critical(nil, "😨 didn't start %s (worker: %s) in time (5m)", s.Name(), s.ID())
        os.Exit(1)
    }

    select {
    case s := <-shutdown:
        slog.Info(nil, "✋  Got a %v, shutting down", s)
    case err := <-s.srv.Done():
        slog.Error(nil, "🛑  Typhon died on us, shutting down: %v", err)
        os.Exit(1)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancel()

    s.Stop(ctx)
    slog.Info(nil, "👋  Server stopped")
}

func (s *Server) Stop(ctx context.Context) {
    done := make(chan bool)
    go func() {
        s.srv.Stop(ctx)

        done <- true
    }()

    select {
    case <-done:
    case <-ctx.Done():
    }
}

func (s *Server) Start(svc typhon.Service) error {
    fmt.Fprintf(os.Stderr, "🦔 💨  %s; version %s | %s, %s/%s\n",
        s.Name(), Version, runtime.Version(), runtime.GOOS, runtime.GOARCH,
    )

    srv, err := typhon.Listen(svc, "")
    if err != nil {
        return err
    }

    slog.Info(nil, "👂 %s listening on %v", s.Name(), srv.Listener().Addr())
    s.srv = srv

    return nil
}