Skip to content
/ runy Public

πŸƒβ€β™‚οΈ Tiny run/startup manager for Go applications that will replace your graceful shutdown implementation

License

Notifications You must be signed in to change notification settings

belo4ya/runy

Repository files navigation

runy - tiny run manager

tag go version go doc go report codecov license

🎯 The goal of the project is to provide developers with the opportunity not to think about the graceful shutdown and not to make mistakes in its implementation in their Go application. Instead, focus on startup components such as HTTP and gRPC servers and other Runnables.

πŸš€ Install

go get -u github.com/belo4ya/runy

Compatibility: Go β‰₯ 1.20

πŸ’‘ Usage

You can import runy using:

import (
	"github.com/belo4ya/runy"
)

Then implement the Runnable interface for your application components:

// Runnable allows a component to be started.
// It's very important that Start blocks until it's done running.
type Runnable interface {
	// Start starts running the component.
	// The component will stop running when the context is closed.
	// Start blocks until the context is closed or an error occurs.
	Start(ctx context.Context) error
}

Finally, register and run your components using runy.Add and runy.Start. Here's a simple application with multiple HTTP servers:

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/belo4ya/runy"
)

func main() {
	// Create a context that's canceled when SIGINT or SIGTERM is received.
	ctx := runy.SetupSignalHandler()

	// Initialize and register application components with runy.
	httpSrv := NewHTTPServer(":8080") // main API server
	mgmtSrv := NewHTTPServer(":8081") // /metrics, /debug/pprof, /healthz, /readyz
	runy.Add(httpSrv, mgmtSrv)

	// Start all components and block until shutdown.
	log.Println("starting app")
	if err := runy.Start(ctx); err != nil {
		log.Fatalf("app error: %v", err)
	}
}

type HTTPServer struct {
	HTTP *http.Server
}

func NewHTTPServer(addr string) *HTTPServer {
	return &HTTPServer{HTTP: &http.Server{Addr: addr}}
}

// Start implements Runnable interface.
// It starts the HTTP server and blocks until context cancellation or server error.
func (s *HTTPServer) Start(ctx context.Context) error {
	errCh := make(chan error, 1)
	go func() {
		log.Printf("http server starts listening on: %s", s.HTTP.Addr)
		if err := s.HTTP.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
			errCh <- fmt.Errorf("http listen and serve: %w", err)
		}
		close(errCh)
	}()

	select {
	case <-ctx.Done():
		log.Println("shutting down http server")
		// Handle cleanup after context cancellation with a reasonable shutdown timeout.
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		if err := s.HTTP.Shutdown(ctx); err != nil {
			return fmt.Errorf("http shutdown: %w", err)
		}
		return nil
	case err := <-errCh:
		return err
	}
}

🧠 Core Concepts

Runnable, SugaredRunnable, Group...

πŸ“š Acknowledgments

The following projects had a particular impact on the design of runy.

About

πŸƒβ€β™‚οΈ Tiny run/startup manager for Go applications that will replace your graceful shutdown implementation

Topics

Resources

License

Stars

Watchers

Forks