fire

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 2 Imported by: 0

README

About

fire provides lightweight lifecycle management for long-running application components such as:

  • HTTP servers
  • gRPC servers
  • consumers
  • workers
  • schedulers
  • background services

Lifecycle

Any task has two phases:

  • Run: start and execute the component.
  • Shutdown: stop the component after its context is canceled.

Task

A Task is anything executable.

fire.Sync(func() error)
fire.Async(func() <-chan error)
fire.TaskFunc(func() Task)

Groups

Group is the main lifecycle coordinator. It can contain any number of Tasks, including other Groups.

By default, tasks in a Group are started in parallel and their shutdown tasks are also started in parallel.

Both Group phases can be changed independently:

g.StartOrder(Parallel)
g.StartOrder(Sequential)
g.ShutdownOrder(Parallel)
g.ShutdownOrder(Sequential)
  • Sequential startup runs tasks in registration order
  • Sequential shutdown always uses reverse registration order

This LIFO behavior is intentional: when components are started sequentially, later components can depend on earlier ones, so they should normally be stopped before their dependencies.

Shutdown

Shutdown registered with task.Shutdown and is itself a Group whose startup order is always Sequential.

For example:

task.Shutdown(
    Sync(func() error { ... }),
    Sync(func() error { ... }),
)

is equivalent to:

g := NewGroup(
    Sync(func() error { ... }),
    Sync(func() error { ... }),
)
g.StartOrder(Sequential)
task.Shutdown(g)

The shutdown group's own shutdown phase is still managed by the normal Group lifecycle rules.

Context during Shutdown

Shutdown starts after the component context is canceled. Therefore, the context passed to a shutdown task is already canceled.

If shutdown work needs a context that remains usable during cleanup, derive it explicitly with context.WithoutCancel:

task.Shutdown(
	func(ctx context.Context) error {
        ctx = context.WithoutCancel(ctx)
        return ...
    },
)

Automatic Shutdown

task.AutoShutdown() can additionally make normal task completion trigger context cancellation, which in turn starts the shutdown phase.

Completion

task.Exited() returns a channel that is closed when the task has completely finished, NOT including its shutdown phase.

task.Wait(ctx) starts the task and blocks until it is completely finished.

Errors

Tasks report errors through their error channels.

Error handlers registered with task.Handle(...) or task.IfError(...) can transform or consume errors before they are propagated to the owning Group.

Tasks may transform or suppress errors using handlers.

t := fire.Sync(run)
t.IfError(func(err error) error {
    return fmt.Errorf("server: %w", err)
})

// or

t := fire.Sync(run)
t.Handle(func(err error) error {
    if err != nil {
        return fmt.Errorf("server: %w", err)
    }
    return nil
})

fire does not assume that every task error should cancel the whole Group. If you want to cancel the whole Group, use task.Cancel().

Example

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer cancel()

server1 := NewServer("0.0.0.0:1111")
server2 := NewServer("0.0.0.0:2222")

g := fire.NewGroup()

t := fire.TaskFunc(server1.Run)
t.IfError(func(err error) error {
    return fmt.Errorf("failed to run server1: %w", err)
})
g.Add(t)

t = fire.TaskFunc(server2.Run)
t.IfError(func(err error) error {
    return fmt.Errorf("failed to run server2: %w", err)
})
g.Add(t)

g.Wait(ctx)

Why fire?

Without fire, lifecycle code usually consists of:

  • goroutines
  • WaitGroups
  • channels
  • signal handling
  • context cancellation
  • shutdown ordering

fire keeps this orchestration in reusable primitives while leaving application-specific error decisions to the caller.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Group

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

Group coordinates the lifecycle of multiple Tasks.

A Group has two independent ordering policies:

  • StartOrder controls how tasks are started.
  • ShutdownOrder controls how task shutdowns are started.

By default, both are Parallel.

A Group can itself be used as a Task, which allows lifecycle hierarchies to be composed of smaller groups.

func NewGroup added in v1.0.1

func NewGroup(tasks ...Task) *Group

NewGroup creates an empty Group and optionally registers the provided tasks.

Tasks are not started until Wait is called.

func (*Group) Add

func (g *Group) Add(tasks ...Task)

Add registers one or more tasks.

Registered tasks are started when Wait is called.

func (*Group) AutoShutdown added in v1.2.0

func (s *Group) AutoShutdown()

AutoShutdown makes normal task completion trigger its shutdown phase.

Without AutoShutdown, Shutdown is normally triggered by context cancellation. With AutoShutdown enabled, returning from Run also cancels the task context, which starts Shutdown.

This is useful for components whose lifetime is defined by the completion of their main operation.

func (*Group) Cancel

func (g *Group) Cancel()

Cancel cancels the Group context.

Cancellation is propagated to all tasks that observe the Group context. For tasks with an autonomous shutdown, cancellation also starts their shutdown phase.

func (*Group) Exited added in v1.2.0

func (g *Group) Exited() chan struct{}

Exited returns a channel that is closed when the Group has completely finished.

Completion includes running tasks, but NOT shutdown tasks.

func (*Group) Handle added in v1.0.1

func (t *Group) Handle(h func(error) error)

Handle registers an error handler.

A handler may transform an error by returning another error. The transformed error is passed to the owning lifecycle level.

func (*Group) IfError added in v1.0.1

func (t *Group) IfError(h func(error) error)

IfError registers an error handler that is called only for non-nil errors.

func (*Group) Shutdown added in v1.2.0

func (s *Group) Shutdown(tasks ...Task)

Shutdown registers tasks that form this component's shutdown phase.

Shutdown is represented internally as a Group with Sequential startup. Therefore, shutdown tasks are always started in the order they are registered.

The context passed to the shutdown task is already canceled because Shutdown is triggered by cancellation of the owning task's context.

Use context.WithoutCancel when cleanup requires a usable context.

func (*Group) ShutdownOrder added in v1.2.0

func (g *Group) ShutdownOrder(order Order)

ShutdownOrder configures how the Group starts task shutdowns.

ShutdownOrder does not change the ordering inside an individual Task's Shutdown group.

func (*Group) StartOrder added in v1.2.0

func (g *Group) StartOrder(order Order)

StartOrder configures how tasks are started.

func (*Group) Wait

func (g *Group) Wait(ctx context.Context)

Wait starts all registered tasks and blocks until they have completely finished.

type Order added in v1.2.0

type Order int

Order defines how tasks are started.

const (
	// Parallel starts all tasks concurrently.
	//
	// This is the default order for both startup and shutdown.
	Parallel Order = iota
	// Sequential starts tasks one by one.
	//
	// Startup follows registration order. Shutdown follows reverse
	// registration order (LIFO).
	Sequential
)

type Task added in v1.0.1

type Task interface {
	// IfError registers a handler invoked only for non-nil errors.
	IfError(func(error) error)
	// Handle registers an error transformation handler.
	Handle(func(error) error)

	// AutoShutdown makes task completion trigger Shutdown.
	AutoShutdown()
	// Shutdown registers the component's shutdown tasks.
	Shutdown(...Task)

	Cancel()
	Exited() chan struct{}
	Wait(ctx context.Context)
	// contains filtered or unexported methods
}

Task represents a unit of work managed by fire.

A Task has a Run phase and an optional Shutdown phase:

Tasks may be nested. Group, TaskFunc, Sync, and Async all implement Task.

func Async added in v1.0.1

func Async(f func() <-chan error) Task

Async creates a Task from an asynchronous function.

The returned error channel represents the lifetime of the Run phase. Closing the channel completes the task.

func AsyncWithContext added in v1.0.1

func AsyncWithContext(f func(ctx context.Context) <-chan error) Task

AsyncWithContext creates a Task from an asynchronous context-aware function.

func Cancel added in v1.1.1

func Cancel(cancel context.CancelFunc) Task

Cancel creates a Task that cancels the supplied context.

This is useful as a small lifecycle primitive, for example when a component needs to cancel another context during Shutdown.

func Sync added in v1.0.1

func Sync(f func() error) Task

Sync creates a Task from a synchronous function.

The function is executed once when the Task starts. Returning from the function completes the Run phase.

func SyncWithContext added in v1.0.1

func SyncWithContext(f functionSync) Task

SyncWithContext creates a Task from a synchronous context-aware function.

The function receives the Task's lifecycle context. The context is canceled when the Task is canceled or its owning Group begins shutdown.

func TaskFunc added in v1.1.0

func TaskFunc(f func() Task) Task

TaskFunc creates a Task lazily using a Task factory.

The factory is called when the Task starts. The returned Task becomes the actual Run phase of the created task.

func TaskFuncWithContext added in v1.1.0

func TaskFuncWithContext(f func(ctx context.Context) Task) Task

TaskFuncWithContext creates a context-aware lazy Task.

The factory receives the Task's lifecycle context and may use it to construct a context-dependent child Task.

func Wait added in v1.2.0

func Wait(event chan struct{}) Task

Wait creates a Task that blocks until event is closed.

This can be used to express lifecycle dependencies between components.

Jump to

Keyboard shortcuts

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