life

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2026 License: MIT Imports: 3 Imported by: 0

README

life

Go Reference Go Report Card License

life is a minimal goroutine lifecycle convergence utility.

This package cannot and should not control goroutine termination. Goroutine shutdown and resource cleanup are the responsibility of the goroutines themselves.

Install

go get github.com/BinGo-Lab-Team/life

Problem

During program shutdown, Go code commonly runs into subtle and dangerous issues:

  • New goroutines start after shutdown has begun
  • sync.WaitGroup.Add races with Wait (undefined behavior)
  • Shutdown signals are scattered and inconsistent
  • Correctness relies on convention rather than enforcement

life solves exactly one problem:

After shutdown begins, no new goroutines are admitted,
and all previously started goroutines can be waited for
without violating sync.WaitGroup semantics.

Non-Goals (Important)

life does NOT guarantee:

  • That goroutines will terminate
  • That resources are released automatically
  • Any timeout or force-stop behavior
  • Any business-level cleanup logic

Goroutine termination is cooperative.

If a goroutine ignores the provided context.Context or blocks indefinitely,
ShutdownAndWait will also block indefinitely.

This is an intentional design boundary.

Design Principles

  • Cooperative cancellation via context.Context
  • Hard admission boundary after shutdown
  • Strict compliance with sync.WaitGroup usage rules
  • Minimal abstraction, no policy or strategy decisions

Basic Usage

package main

import (
	"context"
	"log"
	"time"

	"github.com/BinGo-Lab-Team/life"
)

func main() {
	l := life.NewLife(context.Background())

	// Start a lifecycle-managed goroutine
	if err := l.TryGo(func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				// Shutdown signal received
				log.Println("worker exiting")
				return
			default:
				// Simulate work
				time.Sleep(100 * time.Millisecond)
			}
		}
	}); err != nil {
		log.Println("failed to start goroutine:", err)
	}

	// Simulate program running
	time.Sleep(1 * time.Second)

	// Initiate shutdown and wait for goroutines to return
	l.ShutdownAndWait()
}

This guarantees the following order:

  1. Stop admitting new goroutines
  2. Cancel the root lifecycle context
  3. Wait for all admitted goroutines to return

API Overview

TryGo(func(ctx context.Context)) error
  • Attempts to start a lifecycle-managed goroutine
  • Returns ErrProgramShutting after shutdown begins
  • The function receives the shared root context

Unless ShutdownAndWait is called concurrently, TryGo is non-blocking and returns immediately.

ShutdownAndWait()
  • Initiates shutdown
  • Blocks new goroutine admission
  • Waits for all started goroutines to return
LifeCtx() context.Context
  • Returns the root lifecycle context
  • Useful for integration with external systems

Intended Use Cases

  • Server / daemon shutdown paths
  • Systems that require strict goroutine lifecycle control
  • Codebases that want to avoid WaitGroup shutdown races

Design Notes

The implementation is intentionally compact but non-trivial.

life prioritizes correctness and invariant enforcement over readability or extensibility.

It is not a general concurrency framework. Policy decisions (timeouts, forced termination, retries) belong at a higher level.

License

This project released under MIT License

Documentation

Overview

Package life provides a minimal and strictly-defined lifecycle controller for goroutines.

The core goal of this package is NOT to "gracefully stop goroutines", but to enforce a set of hard concurrency invariants around goroutine admission and shutdown:

  1. Goroutines may only be started via TryGo.
  2. Once shutdown begins, all new TryGo calls are rejected.
  3. ShutdownAndWait waits only for goroutines that actually return.
  4. sync.WaitGroup.Add is never called concurrently with Wait.

IMPORTANT DISCLAIMER:

Life DOES NOT guarantee that goroutines will terminate. Goroutine termination is cooperative and entirely the responsibility of the caller. Goroutines must observe the provided context and return in a timely manner.

If a goroutine ignores the context, blocks indefinitely, or leaks, ShutdownAndWait will block indefinitely as well.

This package intentionally does not attempt to forcibly stop goroutines. It only provides correct accounting, ordering, and shutdown signaling.

Index

Constants

This section is empty.

Variables

View Source
var ErrShuttingDown = errors.New("life shutting down")

ErrShuttingDown is returned by TryGo when the lifecycle has already entered shutdown state.

Once this error is returned, no new goroutines will be started for the lifetime of the Life instance.

Functions

This section is empty.

Types

type Life

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

Life manages the lifecycle boundaries of a group of goroutines.

Life guarantees *admission correctness* and *shutdown ordering*, but explicitly does NOT guarantee goroutine termination.

All goroutines started via TryGo:

  • Share the same root lifecycle context.
  • Are counted exactly once in the internal WaitGroup.
  • Will be waited for during shutdown *only if they return*.

Life enforces a strict separation between:

  • The "running" phase, where new goroutines may be admitted.
  • The "shutdown" phase, where no new goroutines are allowed and the system waits for existing ones to exit.

The design ensures full compliance with sync.WaitGroup's contract: WaitGroup.Add is never called concurrently with Wait.

func NewLife

func NewLife(parent context.Context) *Life

NewLife creates a new Life instance with a root lifecycle context.

The returned Life derives its root context from the provided parent. Cancellation of either the parent context or the Life itself will transition the lifecycle into shutdown state.

NewLife does not start any goroutines. Goroutines are only created via TryGo after construction.

The caller is responsible for eventually calling ShutdownAndWait to release resources and wait for managed goroutines to return.

func (*Life) LifeCtx

func (l *Life) LifeCtx() context.Context

LifeCtx returns the root lifecycle context.

The returned context is canceled when ShutdownAndWait is called. Goroutines are expected to observe ctx.Done() and exit cooperatively.

func (*Life) ShutdownAndWait

func (l *Life) ShutdownAndWait()

ShutdownAndWait initiates shutdown and blocks until all lifecycle-managed goroutines have returned.

The shutdown sequence is:

  1. Cancel the root context, signaling all goroutines to stop.
  2. Acquire the exclusive lock, blocking all concurrent TryGo calls and waiting for in-flight TryGo calls to finish Add.
  3. Call WaitGroup.Wait after no further Add calls are possible.

NOTE:

ShutdownAndWait does NOT forcibly stop goroutines. If any goroutine fails to return (e.g., ignores context cancellation or blocks indefinitely), this method will block indefinitely.

func (*Life) TryGo

func (l *Life) TryGo(f func(life context.Context)) error

TryGo attempts to start a new goroutine bound to the lifecycle.

If the lifecycle is already shutting down, TryGo returns ErrShuttingDown and the function is not executed.

The provided function receives the root lifecycle context. The function is expected to observe ctx.Done() and return.

TryGo guarantees:

  • No goroutine is started after shutdown begins.
  • WaitGroup.Add is never called concurrently with Wait.
  • Unless ShutdownAndWait is called concurrently, TryGo is non-blocking and returns immediately.

TryGo DOES NOT guarantee that the goroutine will exit; correct shutdown behavior requires cooperation from the function.

Jump to

Keyboard shortcuts

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