oklog/run

repository·main·Indexed 23 days ago

https://github.com/oklog/run

A Go library providing a universal mechanism to manage goroutine lifecycles using a group of actors. It orchestrates multiple concurrent tasks via run.Group, ensuring that when the first actor exits, all other actors are signaled to shut down through their respective interrupt functions. The package includes utilities for managing context.Context, net.Listener, io.ReadCloser, and http.Server lifecycles, as well as specialized handlers for OS signals and context cancellation.

Tokens
1.7K
Snippets
8
Records
13
Agent score
81%

What's inside oklog/run

  1. How run.Group manages goroutine lifecycles

    main

    A run.Group is a mechanism to orchestrate multiple goroutines as a single unit. You manage lifecycles by adding actors to a zero-value run.Group.

    An actor consists of two functions:

    1. An execute function: This function runs synchronously within its own goroutine.
    2. An interrupt function: This function is called when any actor in the group exits. Its purpose is to signal the execute function to return (e.g., by closing a connection or canceling a context).

    When you call Run(), the group concurrently executes all actors. The group waits until the first actor exits, then invokes the interrupt functions for all other actors. Run() only returns to the caller once all actors have fully returned.

  2. Manage context.Context lifecycles with run.Group

    main

    You can use run.Group to ensure that a process tied to a context.Context is canceled when the group shuts down. The interrupt function should call the context's cancel function.

    ctx, cancel := context.WithCancel(context.Background())
    g.Add(func() error {
    	return myProcess(ctx, ...)
    }, func(error) {
    	cancel()
    })
  3. Manage net.Listener lifecycles with run.Group

    main

    When running a network service, use the net.Listener.Close() method in the interrupt function to force the server's ListenAndServe loop to exit.

    ln, _ := net.Listen("tcp", ":8080")
    g.Add(func() error {
    	return http.Serve(ln, nil)
    }, func(error) {
    	ln.Close()
    })
  4. Perform graceful shutdown of an http.Server

    main

    To shut down an http.Server gracefully, use the Shutdown method within the interrupt function. This typically involves creating a context with a timeout to ensure the shutdown process does not hang indefinitely.

    httpServer := &http.Server{
    	Addr:    "localhost:8080",
    	Handler: ...,
    }
    g.Add(func() error {
    	return httpServer.ListenAndServe()
    }, func(error) {
    	ctx, cancel := context.WithTimeout(context.TODO(), 3*time.Second)
    	defer cancel()
    	httpServer.Shutdown(ctx)
    })
  5. Manage io.ReadCloser lifecycles with run.Group

    main

    For tasks involving stream processing (like a scanner on a connection), use the Close() method of the io.ReadCloser in the interrupt function to terminate the processing loop.

    var conn io.ReadCloser = ...
    g.Add(func() error {
    	s := bufio.NewScanner(conn)
    	for s.Scan() {
    		println(s.Text())
    	}
    	return s.Err()
    }, func(error) {
    	conn.Close()
    })
  6. Manage goroutine lifecycles with run.Group

    main

    The run.Group type is used to collect multiple actors (functions) and run them concurrently. It provides deterministic teardown: when the first actor returns (either normally or with an error), all other actors are interrupted. Run() blocks until all actors have exited and returns the error from the first actor that stopped.

    To use a Group:

    1. Initialize a Group (the zero value is ready to use).
    2. Use Add(execute func() error, interrupt func(error)) to register actors.
    3. Call Run() to start execution.

    Important Requirements for Actors:

    • Pre-emptability: The execute function must be able to return when the interrupt function is called. If interrupt is invoked, execute must exit.
    • Idempotency: It must be safe to call the interrupt function even after execute has already returned.
    • Error Propagation: The error returned by the first exiting actor is passed to the interrupt functions of all other actors.
  7. Create an actor from a context with ContextHandler

    main
    Use ContextHandler(ctx context.Context) to create an actor (an execute and interrupt function pair) that terminates when the provided context is canceled. The execute function returns the context error when the context is done, and the interrupt function triggers the cancellation of the context.
  8. Create an actor from OS signals with SignalHandler

    main

    Use SignalHandler(ctx context.Context, signals ...os.Signal) to create an actor that terminates when the process receives one of the specified signals, or when the parent context is canceled.

    • If no signals are provided, the actor terminates on any signal (per signal.Notify behavior).
    • If a signal is received, execute returns a SignalError containing the received signal.
    • If the context is canceled, execute returns the context error.
    • The interrupt function cancels the internal context used by the handler.
  9. Handle signal errors with ErrSignal

    main

    When using SignalHandler, if the actor terminates due to a received signal, the execute function returns a SignalError.

    Because of the way SignalError is implemented, you should use errors.Is(err, ErrSignal) to check if the error was caused by a signal. Only use errors.As if you specifically need to access the Signal field within the SignalError struct.

  10. Run actors in a run.Group

    main

    The Run method starts all registered actors concurrently.

    Behavior:

    • If no actors have been added, Run returns nil immediately.
    • It blocks until all actors have exited.
    • When the first actor returns, Run triggers the interrupt function for all other actors, passing the first actor's error to them.
    • It returns the error produced by the first actor that exited.
    func (g *Group) Run() error
  11. SignalError type

    main

    The SignalError type is returned by SignalHandler when a signal triggers termination. It contains the specific os.Signal that was received.

    type SignalError struct {
    	Signal os.Signal
    }
  12. ErrSignal error variable

    main

    The ErrSignal variable is the sentinel error used to identify errors returned by SignalHandler when a signal is received. Use errors.Is(err, run.ErrSignal) to detect it.

    var ErrSignal = errors.New("signal error")