run

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2024 License: Apache-2.0 Imports: 4 Imported by: 19

README

run

Go Reference Go Report Card

Motivation

A program will likely have many long lived daemons running concurrently in go routines. An HTTP or gRPC server, Kafka consumer, etc. When the program needs to shutdown, it should do so gracefully by telling and waiting for every concurrent daemon to shutdown. There are many ways in which these semantics could be implemented and Peter Bourgon compares and contrasts many of them in this video. He recommends the pattern implemented in oklog/run. After 5+ years of using this project, there were various enhancements and wrappers that we made over time to improve the developer experience. This project open sources them. You could think of this as the next generation of oklog/run.

Semantics

  1. Invoke Run for every runnable in the group concurrently.
  2. Wait for the first one to return.
  3. Cancel the context which will signal implementations not using Close to shutdown.
  4. Invoke Close for every runnable in the group concurrently. If this method is not implemented, it means that ForwardCompatibility, whose implementation immediately returns nil, is embedded.
  5. Wait for every Close to return
  6. Wait for every Run to return
  7. Return the error, if any, from step 2.

Usage

Note that while we use the term daemon, this also includes clients that must be properly shutdown down. Examples of these include a syslogger or OTEL exporter that must be properly flushed, a Redis or PostgreSQL connection that must be closed, etc.

Every daemon must implement the Runnable interface. While there are many methods on this interface, the only required method is Run(context.Context). This is made possible through the embeddable ForwardCompatibility. Here is a example implementing a process manager.

type process struct {
  run.ForwardCompatibility
}

func (*process) Run(ctx context.Context) error {
  ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
  defer cancel()

  <-ctx.Done()
  return ctx.Err()
}

In this example, we run until (1) we need to exit because we received a signal or (2) we're being told to exit by the parent. There are some examples where it's cleaner to make use of the Close(context.Context) method on the Runnable interface. An HTTP server is such an example.

type server struct {
  *http.Server
  run.ForwardCompatibility
}

func (s *server) Run(context.Context) error {
  return s.ListenAndServe() 
}

func (s *server) Close(ctx context.Context) error {
  return s.Shutdown(ctx)
}

A set of Runnables are then run as a group.

group := New()

group.Always(new(process))
group.Always(new(server))

group.Run()

Contributions

The contrib package provides runnable implementations for common use cases.

Comparisons

The main comparison to draw is with oklog/run. While this project build on the original codebase, it is different in the following ways (terms are relative to oklog/run) 👇

  1. Actor's interrupts are not invoked synchronously. Rather, the code invokes them concurrently and then waits for all of them to return. This allows all actors to make progress even if there is one that is "stuck" gracefully shutting down. This technical modifies the semantics if clients relied on an ordered shutdown. However, in our many years of using this, we never relied on that.
  2. Provide the Runnable interface as syntactic sugar to make it easier to implement and wire up.
  3. If a client wants to be more idiomatic, pass a context into an actor's execution allowing for graceful shutdown without implementing an interrupt.
  4. Introduce an embeddable type allowing for forward compatibility of the Runnable interface in the same way that protoc-gen-grpc-go does. There is no way to require its embedding however.
  5. Allow actors to be added. This is useful when actors are conditionally executed based on the result of a runtime parameter (i.e. viper.GetBool("my_http_server.enabled")).
  6. Add new methods, in addition to Run and Close, to the Runnable interface to improve observability and assess health. See the documentation for more details.
  7. Allow the shutdown to be preempted by a context.

Documentation

Overview

Package run does something.

Example
package main

import (
	"time"

	"github.com/superblocksteam/run"
	"github.com/superblocksteam/run/contrib/preempt"
)

func main() {
	run.Add(true, preempt.New(100*time.Millisecond))

	run.Run()
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Add

func Add(when bool, runnables ...Runnable)

func Alive

func Alive() bool

func Always

func Always(runnables ...Runnable)

func Run

func Run() error

Types

type ForwardCompatibility

type ForwardCompatibility struct{}

ForwardCompatibility provides a mechanism that allows Runnables to always be forwards compatible with future version of the Runnable interface. The inspiration for this pattern comes from the Protobuf extension protoc-gen-grpc-go. We do not require it's embedding but it is highly recommended to ensure forwards compatibility.

func (ForwardCompatibility) Alive

func (ForwardCompatibility) Alive() bool

func (ForwardCompatibility) Close

func (ForwardCompatibility) Fields

func (ForwardCompatibility) Fields() []slog.Attr

func (ForwardCompatibility) Name

func (ForwardCompatibility) Run

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group manages a collection of Runnables.

func New

func New(options ...Option) *Group

New is syntactic sugar for creating a new Group with the provided functional options.

func (*Group) Add

func (g *Group) Add(when bool, runnables ...Runnable)

Add appends each Runnable to the group if the condition is met.

func (*Group) Alive

func (g *Group) Alive() bool

Alive assess the liveness of all registered Runnables.

func (*Group) Always

func (g *Group) Always(runnables ...Runnable)

Always adds each Runnable to the group.

func (*Group) Run

func (g *Group) Run() error

Run invokes and manages all registered Runnables.

  1. Invoke Run on each Runnable concurrently.
  2. Wait for the first Runnable to return.
  3. Cancel the context passed to Run.
  4. Invoke Close on each Runnable concurrently.
  5. Wait for all Close methods to return.
  6. Wait for all Run methods to return.

It returns the initial error.

type Option

type Option func(*Group)

func WithCloseTimeout

func WithCloseTimeout(duration time.Duration) Option

WithCloseTimeout

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger is a functional option for setting the logger.

type Runnable

type Runnable interface {
	// Run is responsible for executing the main logic of the component
	// and is expected to run until it needs to shut down. Cancellation
	// of the provided context signals that the component should shut down
	// gracefully. If the Runnable implements the Close method than this
	// context can be ignored.
	//
	// Implementations must insure that instantiations of things to be
	// shutdown do not leak outside of this method (i.e. a constructor
	// calling net.Listen) as the Close method may not be called.
	Run(context.Context) error

	// Close is responsible for gracefully shutting down the component. It
	// can either initiate the shutdown process and return or wait for the
	// shutdown process to complete before returning. This method should
	// ensure that all resources used by the component are properly released
	// and any necessary cleanup is performed. If this method is not implemented
	// it is expected that the Run method properly handle context cancellation.
	Close(context.Context) error

	// Alive assesses whether the Runnable has
	// properly been initialized and is active.
	Alive() bool

	// Name returns the name of the Runnable.
	Name() string

	// Fields allows clients to attach additional fields
	// to every log message this library produces.
	Fields() []slog.Attr
}

Directories

Path Synopsis
contrib

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL