svcinit

package module
v2.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 25, 2025 License: MIT Imports: 15 Imported by: 0

README

svcinit

GoDoc

Features

  • manages start/stop ordering using stages.
  • start tasks can stop with or without context cancellation.
  • ensures no race condition if any starting job returns before all jobs initialized.

Example

import (
    "context"
    "errors"
    "fmt"
    "net"
    "net/http"
    "os"
    "syscall"
    "time"

    "github.com/rrgmc/svcinit/v2"
)

// healthService wraps an HTTP service in a [svcinit.Task] interface.
type healthService struct {
    server *http.Server
}

var _ svcinit.Task = (*healthService)(nil)

func (s *healthService) Run(ctx context.Context, step svcinit.Step) error {
    switch step {
    case svcinit.StepSetup:
        // initialize the service in the setup step.
        s.server = &http.Server{
            Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
            }),
            Addr: ":8081",
        }
    case svcinit.StepStart:
        s.server.BaseContext = func(net.Listener) context.Context {
            return ctx
        }
        return s.server.ListenAndServe()
    case svcinit.StepStop:
        return s.server.Shutdown(ctx)
    case svcinit.StepPreStop:
        // called just before shutdown starts.
        // This could be used to make the readiness probe to fail during shutdown for example.
    default:
    }
    return nil
}

func ExampleManager() {
    ctx := context.Background()

    // create health HTTP server
    healthHTTPServer := &healthService{}

    // create core HTTP server
    var httpServer *http.Server

    sinit, err := svcinit.New(
        // initialization in 2 stages. Initialization is done in stage order, and shutdown in reverse stage order.
        // all tasks added to the same stage are started/stopped in parallel.
        svcinit.WithStages(svcinit.StageDefault, "manage", "service"),
        // use a context with a 10-second cancellation in the stop tasks.
        svcinit.WithShutdownTimeout(10*time.Second),
        // some tasks may not check context cancellation, set enforce to true to give up waiting after the shutdown timeout.
        // The default is true.
        svcinit.WithEnforceShutdownTimeout(true),
    )
    if err != nil {
        fmt.Println(err)
        return
    }

    // add a task to start health HTTP server before the service, and stop it after.
    sinit.AddTask("manage", healthHTTPServer)

    // add a task to start the core HTTP server.
    sinit.
        AddTask("service", svcinit.BuildTask(
            svcinit.WithSetup(func(ctx context.Context) error {
                // initialize the service in the setup step.
                // as this may take some time in bigger services, initializing here allows other tasks to setup
                // at the same time.
                httpServer = &http.Server{
                    Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                        w.WriteHeader(http.StatusOK)
                    }),
                    Addr: ":8080",
                }
                return nil
            }),
            svcinit.WithStart(func(ctx context.Context) error {
                httpServer.BaseContext = func(net.Listener) context.Context {
                    return ctx
                }
                return httpServer.ListenAndServe()
            }),
            // stop the service. By default, the context is NOT cancelled, this method must arrange for the start
            // function to end.
            // Use [svcinit.WithCancelContext] if you want the context to be cancelled automatically after the
            // first task finishes.
            svcinit.WithStop(func(ctx context.Context) error {
                return httpServer.Shutdown(ctx)
            }),
        ),
        // svcinit.WithCancelContext(true), // would cancel the "WithStart" context before calling "WithStop".
        )

    // shutdown on OS signal.
    sinit.AddTask(svcinit.StageDefault, svcinit.SignalTask(os.Interrupt, syscall.SIGTERM))

    // sleep 100ms and shutdown.
    sinit.AddTask(svcinit.StageDefault, svcinit.TimeoutTask(100*time.Millisecond,
        svcinit.WithTimeoutTaskError(errors.New("timed out"))))

    err = sinit.Run(ctx)
    if err != nil {
        fmt.Println("err:", err)
    }

    // Output: err: timed out
}

Author

Rangel Reale ([email protected])

Documentation

Index

Examples

Constants

View Source
const (
	StageDefault = "default"
)

Variables

View Source
var (
	ErrExit               = errors.New("normal exit")
	ErrInvalidStage       = errors.New("invalid stage")
	ErrInvalidTaskStep    = errors.New("invalid step for task")
	ErrInvalidStepOrder   = errors.New("invalid step order")
	ErrAlreadyRunning     = errors.New("already running")
	ErrInitialization     = errors.New("initialization error")
	ErrNoStartTask        = errors.New("no start tasks available")
	ErrNilTask            = errors.New("nil task")
	ErrNoStage            = errors.New("no stages available")
	ErrShutdownTimeout    = errors.New("shutdown timeout")
	ErrAlreadyInitialized = errors.New("already initialized")
	ErrNotInitialized     = errors.New("not initialized")
	ErrDuplicateStep      = errors.New("duplicate step")
)
View Source
var (
	ErrAlreadyResolved = errors.New("already resolved")
	ErrNotResolved     = errors.New("not resolved")
)

Functions

func CauseFromContext

func CauseFromContext(ctx context.Context) (error, bool)

CauseFromContext gets the stop cause from the context, if available.

func TaskDescription added in v2.3.4

func TaskDescription(task Task) string

TaskDescription returns the task description.

Types

type BaseOverloadedTask added in v2.2.0

type BaseOverloadedTask struct {
	Task Task
}

BaseOverloadedTask wraps and task and forwards TaskOptions and TaskSteps. It doesn't implement TaskWithWrapped.

func (*BaseOverloadedTask) String added in v2.3.1

func (t *BaseOverloadedTask) String() string

func (*BaseOverloadedTask) TaskOptions added in v2.2.0

func (t *BaseOverloadedTask) TaskOptions() []TaskInstanceOption

func (*BaseOverloadedTask) TaskSteps added in v2.2.0

func (t *BaseOverloadedTask) TaskSteps() []Step

type BaseWrappedTask added in v2.2.0

type BaseWrappedTask struct {
	*BaseOverloadedTask
}

BaseWrappedTask wraps and task and forwards TaskOptions and TaskSteps. It implements TaskWithWrapped.

func NewBaseWrappedTask added in v2.2.0

func NewBaseWrappedTask(task Task) *BaseWrappedTask

func (*BaseWrappedTask) Run added in v2.2.0

func (t *BaseWrappedTask) Run(ctx context.Context, step Step) error

func (*BaseWrappedTask) WrappedTask added in v2.2.0

func (t *BaseWrappedTask) WrappedTask() Task

type CallbackStep

type CallbackStep int
const (
	CallbackStepBefore CallbackStep = iota
	CallbackStepAfter
)

func (CallbackStep) String

func (s CallbackStep) String() string

type Future added in v2.2.0

type Future[T any] interface {
	Value(options ...FutureValueOption) (T, error)
	Done() <-chan struct{}
}

Future is a proxy for a result that is initially unknown.

type FutureResolver added in v2.2.0

type FutureResolver[T any] interface {
	Future[T]
	Resolve(value T)
	ResolveError(err error)
}

func NewFuture added in v2.2.0

func NewFuture[T any]() FutureResolver[T]

type FutureValueOption added in v2.2.0

type FutureValueOption func(*futureValueOptions)

func WithFutureCtx added in v2.2.0

func WithFutureCtx(ctx context.Context) FutureValueOption

func WithFutureWait added in v2.2.0

func WithFutureWait() FutureValueOption

type Manager

type Manager struct {
	// contains filtered or unexported fields
}
Example
package main

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"syscall"
	"time"

	"github.com/rrgmc/svcinit/v2"
)

// healthService wraps an HTTP service in a [svcinit.Task] interface.
type healthService struct {
	server *http.Server
}

var _ svcinit.Task = (*healthService)(nil)

func (s *healthService) Run(ctx context.Context, step svcinit.Step) error {
	switch step {
	case svcinit.StepSetup:
		// initialize the service in the setup step.
		s.server = &http.Server{
			Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusOK)
			}),
			Addr: ":8081",
		}
	case svcinit.StepStart:
		s.server.BaseContext = func(net.Listener) context.Context {
			return ctx
		}
		return s.server.ListenAndServe()
	case svcinit.StepStop:
		return s.server.Shutdown(ctx)
	case svcinit.StepPreStop:
		// called just before shutdown starts.
		// This could be used to make the readiness probe to fail during shutdown for example.
	default:
	}
	return nil
}

func main() {
	ctx := context.Background()

	// create health HTTP server
	healthHTTPServer := &healthService{}

	// create core HTTP server
	var httpServer *http.Server

	sinit, err := svcinit.New(
		// initialization in 2 stages. Initialization is done in stage order, and shutdown in reverse stage order.
		// all tasks added to the same stage are started/stopped in parallel.
		svcinit.WithStages(svcinit.StageDefault, "manage", "service"),
		// use a context with a 10-second cancellation in the stop tasks.
		svcinit.WithShutdownTimeout(10*time.Second),
		// some tasks may not check context cancellation, set enforce to true to give up waiting after the shutdown timeout.
		// The default is true.
		svcinit.WithEnforceShutdownTimeout(true),
	)
	if err != nil {
		fmt.Println(err)
		return
	}

	// add a task to start health HTTP server before the service, and stop it after.
	sinit.AddTask("manage", healthHTTPServer)

	// add a task to start the core HTTP server.
	sinit.
		AddTask("service", svcinit.BuildTask(
			svcinit.WithSetup(func(ctx context.Context) error {
				// initialize the service in the setup step.
				// as this may take some time in bigger services, initializing here allows other tasks to setup
				// at the same time.
				httpServer = &http.Server{
					Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
						w.WriteHeader(http.StatusOK)
					}),
					Addr: ":8080",
				}
				return nil
			}),
			svcinit.WithStart(func(ctx context.Context) error {
				httpServer.BaseContext = func(net.Listener) context.Context {
					return ctx
				}
				return httpServer.ListenAndServe()
			}),
			// stop the service. By default, the context is NOT cancelled, this method must arrange for the start
			// function to end.
			// Use [svcinit.WithCancelContext] if you want the context to be cancelled automatically after the
			// first task finishes.
			svcinit.WithStop(func(ctx context.Context) error {
				return httpServer.Shutdown(ctx)
			}),
		),
		// svcinit.WithCancelContext(true), // would cancel the "WithStart" context before calling "WithStop".
		)

	// shutdown on OS signal.
	sinit.AddTask(svcinit.StageDefault, svcinit.SignalTask(os.Interrupt, syscall.SIGTERM))

	// sleep 100ms and shutdown.
	sinit.AddTask(svcinit.StageDefault, svcinit.TimeoutTask(100*time.Millisecond,
		svcinit.WithTimeoutTaskError(errors.New("timed out"))))

	err = sinit.Run(ctx)
	if err != nil {
		fmt.Println("err:", err)
	}

}
Output:

err: timed out

func New

func New(options ...Option) (*Manager, error)

func (*Manager) AddInitError added in v2.2.0

func (m *Manager) AddInitError(err error)

func (*Manager) AddService added in v2.0.1

func (m *Manager) AddService(stage string, service Service, options ...TaskOption)

func (*Manager) AddTask

func (m *Manager) AddTask(stage string, task Task, options ...TaskOption)

func (*Manager) AddTaskFunc added in v2.0.1

func (m *Manager) AddTaskFunc(stage string, f TaskFunc, options ...TaskOption)

func (*Manager) IsRunning

func (m *Manager) IsRunning() bool

func (*Manager) Run

func (m *Manager) Run(ctx context.Context, options ...RunOption) error

func (*Manager) RunWithStopErrors

func (m *Manager) RunWithStopErrors(ctx context.Context, options ...RunOption) (cause error, stopErr error)

func (*Manager) Shutdown

func (m *Manager) Shutdown()

Shutdown starts the shutdown process as if a task finished.

func (*Manager) Stages

func (m *Manager) Stages() []string

type ManagerCallback

type ManagerCallback interface {
	Callback(ctx context.Context, stage string, step Step, callbackStep CallbackStep)
}

ManagerCallback is a callback for manager events. A cause may be set in the context. Use CauseFromContext to check.

type ManagerCallbackFunc

type ManagerCallbackFunc func(ctx context.Context, stage string, step Step, callbackStep CallbackStep)

func (ManagerCallbackFunc) Callback

func (f ManagerCallbackFunc) Callback(ctx context.Context, stage string, step Step, callbackStep CallbackStep)

type Option

type Option func(*Manager)

func WithEnforceShutdownTimeout

func WithEnforceShutdownTimeout(enforceShutdownTimeout bool) Option

WithEnforceShutdownTimeout don't wait for all shutdown tasks to complete if they are over the shutdown timeout. Usually the shutdown timeout only sets a timeout in the context, but it can't guarantee that all tasks will follow it. Default is true.

func WithLogger

func WithLogger(logger *slog.Logger) Option

func WithManagerCallback

func WithManagerCallback(callbacks ...ManagerCallback) Option

func WithShutdownTimeout

func WithShutdownTimeout(shutdownTimeout time.Duration) Option

WithShutdownTimeout sets a shutdown timeout. The default is 10 seconds. If less then or equal to 0, no shutdown timeout will be set.

func WithStages

func WithStages(stages ...string) Option

func WithTaskCallback

func WithTaskCallback(callbacks ...TaskCallback) Option

type RunOption

type RunOption func(options *runOptions)

func WithRunShutdownContext

func WithRunShutdownContext(ctx context.Context) RunOption

WithRunShutdownContext sets a context to use for shutdown. If not set, "context.WithoutCancel(baseContext)" will be used.

type Service

type Service interface {
	Start(ctx context.Context) error
	Stop(ctx context.Context) error
}

Service is an abstraction of Task as an interface, for convenience. Use ServiceAsTask do the wrapping.

type ServiceTask

type ServiceTask interface {
	Task
	Service() Service
}

ServiceTask allows getting the source Service of the Task.

func ServiceAsTask

func ServiceAsTask(service Service) ServiceTask

ServiceAsTask wraps a Service into a Task.

type ServiceWithPreStop

type ServiceWithPreStop interface {
	PreStop(ctx context.Context) error
}

ServiceWithPreStop is a Service which has a PreStop step.

type ServiceWithSetup

type ServiceWithSetup interface {
	Setup(ctx context.Context) error
	Teardown(ctx context.Context) error
}

ServiceWithSetup is a Service which has a Setup step.

type SignalError

type SignalError struct {
	Signal os.Signal
}

SignalError is returned from SignalTask if the signal was received.

func (SignalError) Error

func (e SignalError) Error() string

type StartStepManager

type StartStepManager interface {
	ContextCancel(cause error) bool
	Finished() <-chan struct{}
	CanContextCancel() bool
	CanFinished() bool
}

func StartStepManagerFromContext

func StartStepManagerFromContext(ctx context.Context) StartStepManager

StartStepManagerFromContext returns a StartStepManager from the stop step's context. If not available returns a noop instance.

type Step

type Step int
const (
	StepSetup Step = iota
	StepStart
	StepPreStop
	StepStop
	StepTeardown
)

func DefaultTaskSteps

func DefaultTaskSteps() []Step

DefaultTaskSteps returns the default value for [TaskSteps.TaskSteps], which is the list of all steps.

func (Step) String

func (s Step) String() string

type Task

type Task interface {
	Run(ctx context.Context, step Step) error
}

func BuildDataTask added in v2.1.0

func BuildDataTask[T any](setupFunc TaskBuildDataSetupFunc[T], options ...TaskBuildDataOption[T]) Task

func BuildTask

func BuildTask(options ...TaskBuildOption) Task

func UnwrapTask

func UnwrapTask(task Task) Task

UnwrapTask unwraps TaskWithWrapped from tasks.

type TaskAndInstanceOption

type TaskAndInstanceOption interface {
	TaskOption
	TaskInstanceOption
}

func WithCancelContext

func WithCancelContext(cancelContext bool) TaskAndInstanceOption

WithCancelContext sets whether to automatically cancel the task start step context when the first task finishes. The default is false, meaning that the stop step should handle to stop the task.

func WithStartStepManager

func WithStartStepManager() TaskAndInstanceOption

WithStartStepManager sets whether to add a StartStepManager to the stop step context. This allows the stop step to cancel the start step context and/or wait for its completion.

type TaskBuildDataFunc added in v2.1.0

type TaskBuildDataFunc[T any] func(ctx context.Context, data T) error

type TaskBuildDataOption added in v2.1.0

type TaskBuildDataOption[T any] func(*taskBuildData[T])

func WithDataDescription added in v2.1.0

func WithDataDescription[T any](description string) TaskBuildDataOption[T]

func WithDataParent added in v2.2.0

func WithDataParent[T any](parent Task) TaskBuildDataOption[T]

func WithDataParentFromSetup added in v2.2.0

func WithDataParentFromSetup[T any](parentFromSetup bool) TaskBuildDataOption[T]

func WithDataPreStop added in v2.1.0

func WithDataPreStop[T any](f TaskBuildDataFunc[T]) TaskBuildDataOption[T]

func WithDataStart added in v2.1.0

func WithDataStart[T any](f TaskBuildDataFunc[T]) TaskBuildDataOption[T]

func WithDataStop added in v2.1.0

func WithDataStop[T any](f TaskBuildDataFunc[T]) TaskBuildDataOption[T]

func WithDataTaskOptions added in v2.1.0

func WithDataTaskOptions[T any](options ...TaskInstanceOption) TaskBuildDataOption[T]

func WithDataTeardown added in v2.1.0

func WithDataTeardown[T any](f TaskBuildDataFunc[T]) TaskBuildDataOption[T]

type TaskBuildDataSetupFunc added in v2.1.0

type TaskBuildDataSetupFunc[T any] func(ctx context.Context) (T, error)

type TaskBuildFunc

type TaskBuildFunc func(ctx context.Context) error

type TaskBuildOption

type TaskBuildOption func(*taskBuild)

func WithDescription added in v2.0.1

func WithDescription(description string) TaskBuildOption

func WithParent added in v2.2.2

func WithParent(parent Task) TaskBuildOption

func WithPreStop

func WithPreStop(f TaskBuildFunc) TaskBuildOption

func WithSetup

func WithSetup(f TaskBuildFunc) TaskBuildOption

func WithStart

func WithStart(f TaskBuildFunc) TaskBuildOption

func WithStep

func WithStep(step Step, f TaskBuildFunc) TaskBuildOption

func WithStop

func WithStop(f TaskBuildFunc) TaskBuildOption

func WithTaskOptions

func WithTaskOptions(options ...TaskInstanceOption) TaskBuildOption

func WithTeardown added in v2.1.0

func WithTeardown(f TaskBuildFunc) TaskBuildOption

type TaskCallback

type TaskCallback interface {
	Callback(ctx context.Context, task Task, stage string, step Step, callbackStep CallbackStep, err error)
}

TaskCallback is a callback for task events. err is only set for CallbackStepAfter.

type TaskCallbackFunc

type TaskCallbackFunc func(ctx context.Context, task Task, stage string, step Step, callbackStep CallbackStep, err error)

func (TaskCallbackFunc) Callback

func (f TaskCallbackFunc) Callback(ctx context.Context, task Task, stage string, step Step, callbackStep CallbackStep, err error)

type TaskFunc

type TaskFunc func(ctx context.Context, step Step) error

func (TaskFunc) Run

func (t TaskFunc) Run(ctx context.Context, step Step) error

func (TaskFunc) String added in v2.0.1

func (t TaskFunc) String() string

type TaskFuture added in v2.2.0

type TaskFuture[T any] interface {
	Task
	Future[T]
}

TaskFuture is a Task with data where the return of the setup step will resolve the Future.

func NewTaskFuture added in v2.2.0

func NewTaskFuture[T any](setupFunc TaskBuildDataSetupFunc[T], options ...TaskBuildDataOption[T]) TaskFuture[T]

type TaskHandler added in v2.0.1

type TaskHandler func(ctx context.Context, task Task, step Step) error

type TaskInstanceOption

type TaskInstanceOption interface {
	// contains filtered or unexported methods
}

type TaskOption

type TaskOption interface {
	// contains filtered or unexported methods
}

func WithCallback

func WithCallback(callbacks ...TaskCallback) TaskOption

WithCallback adds callbacks for the task.

func WithHandler added in v2.0.1

func WithHandler(handler TaskHandler) TaskOption

WithHandler adds a task handler.

type TaskSignalTask

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

func SignalTask

func SignalTask(signals ...os.Signal) *TaskSignalTask

SignalTask returns a task that returns when one of the passed OS signals is received.

func (*TaskSignalTask) Run

func (t *TaskSignalTask) Run(ctx context.Context, step Step) error

func (*TaskSignalTask) Signals

func (t *TaskSignalTask) Signals() []os.Signal

func (*TaskSignalTask) String added in v2.3.1

func (t *TaskSignalTask) String() string

func (*TaskSignalTask) TaskOptions

func (t *TaskSignalTask) TaskOptions() []TaskInstanceOption

func (*TaskSignalTask) TaskSteps

func (t *TaskSignalTask) TaskSteps() []Step

type TaskSteps

type TaskSteps interface {
	TaskSteps() []Step
}

TaskSteps sets the steps that the task implements. They will be the only ones called.

type TaskTimeoutTask

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

func TimeoutTask

func TimeoutTask(timeout time.Duration, options ...TimeoutTaskOption) *TaskTimeoutTask

TimeoutTask stops the task after the specified timeout, or the context is done. By default, a TimeoutError is returned on timeout.

func (*TaskTimeoutTask) Run

func (t *TaskTimeoutTask) Run(ctx context.Context, step Step) error

func (*TaskTimeoutTask) String added in v2.3.1

func (t *TaskTimeoutTask) String() string

func (*TaskTimeoutTask) TaskOptions

func (t *TaskTimeoutTask) TaskOptions() []TaskInstanceOption

func (*TaskTimeoutTask) TaskSteps

func (t *TaskTimeoutTask) TaskSteps() []Step

func (*TaskTimeoutTask) Timeout

func (t *TaskTimeoutTask) Timeout() time.Duration

type TaskWithInitError added in v2.2.0

type TaskWithInitError interface {
	TaskInitError() error
}

TaskWithInitError allows a task to report an initialization error. The error might be nil.

type TaskWithOptions

type TaskWithOptions interface {
	TaskOptions() []TaskInstanceOption
}

TaskWithOptions allows the task to set some of the task options. They have priority over options set via Manager.AddTask.

type TaskWithWrapped

type TaskWithWrapped interface {
	Task
	WrappedTask() Task
}

TaskWithWrapped is a task which was wrapped from another Task.

func WrapTask

func WrapTask(task Task, options ...WrapTaskOption) TaskWithWrapped

WrapTask wraps a task in a TaskWithWrapped, allowing the handler to be customized.

type TimeoutError

type TimeoutError struct {
	Timeout time.Duration
}

TimeoutError is returned by TimeoutTask by default.

func (TimeoutError) Error

func (e TimeoutError) Error() string

type TimeoutTaskOption

type TimeoutTaskOption func(task *TaskTimeoutTask)

func WithTimeoutTaskError

func WithTimeoutTaskError(timeoutErr error) TimeoutTaskOption

WithTimeoutTaskError sets an error to be returned from the timeout task instead of TimeoutError.

func WithoutTimeoutTaskError

func WithoutTimeoutTaskError() TimeoutTaskOption

WithoutTimeoutTaskError returns a nil error in case of timeout.

type WrapTaskOption

type WrapTaskOption func(task *wrappedTask)

func WithWrapDescription added in v2.0.1

func WithWrapDescription(description string) WrapTaskOption

WithWrapDescription sets the task description.

func WithWrapTaskHandler

func WithWrapTaskHandler(handler TaskHandler) WrapTaskOption

WithWrapTaskHandler sets an optional handler for the task.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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