suture

repository·master·Indexed 23 days ago

https://github.com/thejerf/suture

A Go library that implements Erlang-inspired supervisor trees to manage the lifecycle of services. Suture provides robust error handling, restart logic, and graceful shutdown capabilities, allowing applications to remain operational even when individual components fail. It includes a Service interface for defining units of work and a Supervisor for managing those services with configurable failure thresholds, backoff behavior, and event hooks.

Tokens
3.8K
Snippets
10
Records
22
Agent score
80%

What's inside suture

  1. Control supervisor restart behavior with special errors

    master

    In Suture v4, services can return specific errors to control how the supervisor tree responds to a service failure:

    • ErrDoNotRestart: Indicates the service should not be restarted, but other services in the tree should remain unaffected.
    • ErrTerminateTree: Indicates the parent service tree should be terminated. Supervisor trees can be configured to either continue terminating upwards or terminate themselves without propagating the termination upwards.
  2. Use the default slog-based logger

    master

    Suture provides a default logger based on Go's slog package. This is located in a separate module to maintain compatibility with older Go versions in the main repository. To use it, you must explicitly add the dependency to your project:

    go get github.com/thejerf/sutureslog
    go get github.com/thejerf/sutureslog
  3. Install and import Suture v4

    master

    Suture provides Erlang-ish supervisor trees for Go to manage service lifecycles and handle failures gracefully. To use the current major version (v4), import the package using the following path:

    import "github.com/thejerf/suture/v4"
    import "github.com/thejerf/suture/v4"
  4. How supervisor failure backoff works

    master

    The supervisor uses an exponential decay model to track failures.

    1. When a failure occurs, the failure count increments by 1.
    2. Every FailureDecay seconds, the failure count is reduced (cut in half) using an exponential function.
    3. If the failure count exceeds FailureThreshold, the supervisor enters a paused state.
    4. In the paused state, the supervisor waits for FailureBackoff (plus optional jitter) before resuming normal operation.
    5. Upon resuming, the failure count is reset to zero.
  5. Start a Supervisor using Serve or ServeBackground

    master

    A supervisor must be started before it can manage services. There are three ways to start a supervisor:

    1. Serve(ctx context.Context): Runs the supervisor in the current goroutine. It blocks until the provided ctx is cancelled. This is typically called in the main function of a program.
    2. ServeBackground(ctx context.Context): Starts the supervisor in a new goroutine and returns a one-buffered channel that receives the error returned by Serve. This is the recommended way to start a supervisor if you want to continue execution in the current goroutine.
    3. Adding it to an existing Supervisor: If you add a Supervisor as a service to another supervisor, it will start when the parent starts.

    Warning: Avoid manually running go supervisor.Serve() because it creates a race condition if you attempt to .Add() services immediately after.

  6. Debug service shutdown issues in Go 1.25 synctest

    master

    Suture supervisors are safe to use in synctest bubbles. If you encounter a panic: deadlock: main bubble goroutine has exited but blocked goroutines remain, it typically means a service is not shutting down correctly when the supervisor stops.

    To fix this:

    1. Identify the blocked goroutine in the stack trace. Look for github.com/thejerf/suture/v4.(*Supervisor).stopSupervisor.
    2. Locate the stack trace pointing to your specific service and the line where it is stuck.
    3. Ensure your service is correctly monitoring and responding to the passed-in context.Context value to trigger a shutdown.
  7. Configure Supervisor failure handling and backoff

    master

    The Spec struct allows you to control how the supervisor reacts to service failures to prevent 'thrashing' (rapidly restarting failing services).

    Key Fields:

    • FailureDecay: The duration over which the failure count is exponentially decayed. Every FailureDecay seconds, the failure count is halved.
    • FailureThreshold: The number of failures required to trigger the backoff mode.
    • FailureBackoff: The duration the supervisor waits before attempting any further restarts once the threshold is reached.
    • BackoffJitter: An implementation of the Jitter interface to add randomness to the backoff time.
    • PassThroughPanics: If true, panics in services will propagate and crash the program instead of being caught and handled by the supervisor.
    • DontPropagateTermination: If true, if a child returns ErrTerminateTree, this supervisor will return ErrDoNotRestart instead of terminating the entire tree.
  8. Convert a DeprecatedService to a Service using AsService

    master

    If you are working with legacy services that implement the DeprecatedService interface, you can wrap them to make them compatible with the modern Service interface using AsService.

    AsService handles the lifecycle transition by:

    1. Running the legacy Serve() method in a separate goroutine.
    2. Monitoring the provided context.Context.
    3. Automatically calling Stop() on the legacy service if the context is cancelled.
    4. Returning nil if the service stops naturally, or the context error if the service is stopped via context cancellation.
  9. Remove and stop a service

    master

    To terminate a specific service managed by a supervisor, use the ServiceToken returned by Add().

    • Remove(id ServiceToken): Attempts to stop the service. This method returns immediately without waiting for the service to actually terminate.
    • RemoveAndWait(id ServiceToken, timeout time.Duration): Attempts to stop the service and waits up to the specified timeout.
      • Returns nil if the service terminated normally.
      • Returns ErrTimeout if the timeout expires or the supervisor terminates before the service stops.
      • Returns ErrWrongSupervisor if the token does not belong to this supervisor.

    Note: A timeout of 0 in RemoveAndWait means it will wait forever.

  10. Add services to a Supervisor

    master

    Use Add(service Service) to register a service with a supervisor.

    • If the supervisor is already running, the service starts immediately.
    • If the supervisor is not yet running, the service will start when the supervisor's Serve method is called.
    • If the service being added is itself a Supervisor (implements HasSupervisor), the child supervisor will automatically inherit the EventHook from the parent supervisor.

    Returns a ServiceToken, which is an opaque identifier used to later remove or terminate that specific service.

  11. Create a Supervisor with New or NewSimple

    master

    A Supervisor manages the lifecycle of services (including other supervisors).

    • Use New(name string, spec Spec) for full control over configuration, such as failure thresholds, backoff behavior, and event hooks.
    • Use NewSimple(name string) for a supervisor with sensible default settings.

    Default Configuration:

    • FailureDecay: 30 seconds (exponential decay of failure count)
    • FailureThreshold: 5 failures
    • FailureBackoff: 15 seconds
    • Timeout: 10 seconds
    • BackoffJitter: DefaultJitter (uniform distribution in [d, 1.5*d))
    • EventHook: Uses log.Print to log service starts, stops, failures, and panics.
  12. Get an Unstopped Service Report

    master

    When a supervisor is stopped, some services might fail to terminate within the configured Timeout.

    UnstoppedServiceReport() returns a report of services that failed to stop. This call blocks until the supervisor has finished its shutdown process.

    Warning: The returned data is subject to TOCTOU (Time-of-check to time-of-use) violations; a service in the report may have stopped by the time you inspect it. Use this information primarily for logging during program teardown.