svcinit

package module
v3.7.2 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2025 License: MIT Imports: 15 Imported by: 0

README

svcinit

GoDoc

svcinit is an initialization system for Go services.

It manages starting and stopping tasks (like a web server), initialization order, correct context handling, without race conditions and goroutine-safe.

It is NOT some kind of dependency injection or application framework like Uber's FX, it could be seen like a more advanced version of github.com/oklog/run.

The library makes it easy to follow common service initialization patterns, like making sure things start in a defined order, correctly doing startup, liveness and readiness probes, context cancellation where the shutdown context is not the same as the startup one (otherwise shutdown tasks would also be cancelled), using resolvable futures to provide data to dependent tasks, and more.

Install

go get github.com/rrgmc/svcinit/v3

Table of Contents

Features

  • stages for managing start/stop ordering. The next stage is only initialized once the previous one was fully started.
  • start, stop, setup and teardown task steps.
  • start steps can stop with or without context cancellation.
  • setup and teardown steps to perform task initialization and finalization. Initialization is done in a goroutine, so for example a health service can correctly manage a startup probe.
  • keeps track of all steps executed, so each step is guaranteed to be called at most once, and any initialization error just calls the stopping steps of what was effectively started.
  • ensures no race conditions, like tasks finishing before all initialization was done.
  • "futures" to manage task dependencies.
  • possibility of the stop step directly managing it's start step, like canceling its context and waiting for its completion.
  • callbacks for all events that happens during execution.
  • the application execution error result will be the error returned by the first start step that finishes.
  • specific implementation using Kubernetes initialization patterns.

Task type

type Step int

const (
    StepSetup Step = iota
    StepStart
    StepStop
    StepTeardown
)

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

Example

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

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

// healthService implements an HTTP server used to serve health probes.
type healthService struct {
    server *http.Server
}

func newHealthService() *healthService {
    return &healthService{
        server: &http.Server{
            Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
            }),
            Addr: ":8081",
        },
    }
}

func (s *healthService) Start(ctx context.Context) error {
    s.server.BaseContext = func(net.Listener) context.Context {
        return ctx
    }
    return s.server.ListenAndServe()
}

func (s *healthService) Stop(ctx context.Context) error {
    return s.server.Shutdown(ctx)
}

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

    sinit, err := svcinit.New(
        // initialization in 3 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 20-second cancellation during shutdown.
        svcinit.WithShutdownTimeout(20*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", svcinit.BuildDataTask[*healthService](
        // the "BuildDataTask" setup callback returns an instance that is sent to all following steps.
        func(ctx context.Context) (*healthService, error) {
            return newHealthService(), nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service *healthService) error {
            return service.Start(ctx)
        }),
        svcinit.WithDataStop(func(ctx context.Context, service *healthService) error {
            return service.Stop(ctx)
        }),
    ))

    // add a task to start the core HTTP server.
    sinit.AddTask("service", svcinit.BuildDataTask[*http.Server](
        func(ctx context.Context) (*http.Server, error) {
            // initialize the service in the setup step.
            // as this may take some time in bigger services, initializing here allows other tasks to initialize
            // at the same time.
            server := &http.Server{
                Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                    w.WriteHeader(http.StatusOK)
                }),
                Addr: ":8080",
            }
            return server, nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service *http.Server) error {
            service.BaseContext = func(net.Listener) context.Context {
                return ctx
            }
            return service.ListenAndServe()
        }),
        // stop the service. By default, the context is NOT cancelled, this method must arrange for the start
        // function to end.
        svcinit.WithDataStop(func(ctx context.Context, service *http.Server) error {
            return service.Shutdown(ctx)
        }),
    ))

    // 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
}

Real world example

This example starts an HTTP server and a (simulated) messaging listener which are the core function of the service. The service will have telemetry, a health HTTP server listening in a different port, and will follow the Kubernetes pattern of having startup, liveness and readiness probes with the correct states at all times.

Full source code in the sample folder.

There is step-by-step description of the complete process after the source code.

import (
    "context"
    "database/sql"
    "fmt"
    "os"
    "syscall"
    "time"

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

const (
    StageManagement = "management" // 1st stage: initialize telemetry, health server and signal handling
    StageInitialize = "initialize" // 2nd stage: initialize data, like DB connections
    StageReady      = "ready"      // 3rd stage: signals probes that the service has completely started
    StageService    = "service"    // 4th state: initialize services
)

var allStages = []string{StageManagement, StageInitialize, StageReady, StageService}

//
// Health webservice
//

type HealthService interface {
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
    ServiceStarted()        // signal the startup / readiness probe that the service is ready
    ServiceTerminating()    // signal the readiness probe that the service is terminating and not ready
    AddDBHealth(db *sql.DB) // add the DB connection to be checked in the readiness probe
}

//
// HTTP webservice
//

type HTTPService interface {
    svcinit.Service // has "Start(ctx) error" and "Stop(ctx) error" methods.
}

//
// Messaging service
//
// Simulates a messaging service receiving and processing messages.
// This specific sample uses a TCP listener for the simulation.
//

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

func main() {
    ctx := context.Background()
    if err := run(ctx); err != nil {
        fmt.Println(err)
    }
}

func run(ctx context.Context) error {
    logger := defaultLogger(os.Stdout)

    sinit, err := svcinit.New(
        svcinit.WithLogger(logger),
        // initialization in 4 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(allStages...),
        // use a context with a 20-second cancellation during shutdown.
        svcinit.WithShutdownTimeout(20*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 {
        return err
    }

    //
    // OpenTelemetry
    //

    // initialize and close OpenTelemetry.
    sinit.AddTask(StageManagement, svcinit.BuildTask(
        svcinit.WithSetup(func(ctx context.Context) error {
            // TODO: OpenTelemetry initialization
            return nil
        }),
        svcinit.WithTeardown(func(ctx context.Context) error {
            // TODO: OpenTelemetry closing/flushing
            return nil
        }),
        svcinit.WithName("telemetry"),
    ))

    // flush the metrics as fast as possible on SIGTERM.
    sinit.AddTask(StageService, svcinit.BuildTask(
        svcinit.WithStop(func(ctx context.Context) error {
            // TODO: flush the current metrics as fast a possible.
            // We may not have enough time if the shutdown takes too long, so do it as early as possible.
            return nil
        }),
        svcinit.WithName("telemetry flush"),
    ))

    //
    // Health service
    //

    // health server must be the first to start and last to stop.
    // created as a future task so it can be accessed by other tasks.
    // other tasks can wait for it to become available.
    healthTask := svcinit.NewTaskFuture[HealthService](
        func(ctx context.Context) (HealthService, error) {
            return NewHealthServiceImpl(), nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service HealthService) error {
            return service.Start(ctx)
        }),
        svcinit.WithDataStop(func(ctx context.Context, service HealthService) error {
            return service.Stop(ctx)
        }),
        svcinit.WithDataName[HealthService]("health service"),
    )
    sinit.AddTask(StageManagement, healthTask)

    // the "ready" stage is executed after all initialization already happened. It is used to signal the
    // startup probes that the service is ready.
    sinit.AddTask(StageReady, svcinit.BuildTask(
        svcinit.WithSetup(func(ctx context.Context) error {
            healthServer, err := healthTask.Value() // get health server from future
            if err != nil {
                return fmt.Errorf("error getting health server: %w", err)
            }
            logger.DebugContext(ctx, "service started, signaling probes")
            healthServer.ServiceStarted()
            return nil
        }),
        svcinit.WithName("health server started probe"),
    ))

    // add a task in the "service" stage, so the stop step is called in parallel with the service stopping ones.
    // This tasks signals the probes that the service is terminating.
    sinit.AddTask(StageService, svcinit.BuildTask(
        svcinit.WithStop(func(ctx context.Context) error {
            healthServer, err := healthTask.Value() // get health server from future
            if err != nil {
                return fmt.Errorf("error getting health server: %s", err)
            }
            logger.DebugContext(ctx, "service terminating, signaling probes")
            healthServer.ServiceTerminating()
            return nil
        }),
        svcinit.WithName("health server terminating probe"),
    ))

    //
    // initialize data to be used by the service, like database and cache connections.
    // A TaskFuture is a Task and a Future at the same time, where the task resolves the future.
    // Following tasks can wait on this future to get the initialized data.
    //
    type initTaskData struct {
        db *sql.DB
    }
    initTask := svcinit.NewTaskFuture[*initTaskData](
        func(ctx context.Context) (data *initTaskData, err error) {
            data = &initTaskData{}

            logger.InfoContext(ctx, "connecting to database")
            // ret.db, err = sql.Open("pgx", "dburl")
            data.db = &sql.DB{}
            if err != nil {
                return nil, err
            }

            // send the initialized DB connection to the health service to be used by the readiness probe.
            healthServer, err := healthTask.Value() // get the health server from the Future.
            if err != nil {
                return nil, err
            }
            healthServer.AddDBHealth(data.db)

            logger.InfoContext(ctx, "data initialization finished")
            return
        },
        svcinit.WithDataTeardown(func(ctx context.Context, data *initTaskData) error {
            logger.InfoContext(ctx, "closing database connection")
            // return data.db.Close()
            return nil
        }),
        svcinit.WithDataName[*initTaskData]("init data"),
    )
    sinit.AddTask(StageInitialize, initTask)

    //
    // initialize and start the HTTP service.
    //
    sinit.AddTask(StageService, svcinit.BuildDataTask[svcinit.Task](
        func(ctx context.Context) (svcinit.Task, error) {
            // using the WithDataParentFromSetup parameter, returning a [svcinit.Task] from this "setup" step
            // sets it as the parent task, and all of its steps are added to this one.
            // This makes Start and Stop be called automatically.
            initData, err := initTask.Value() // get the init value from the future declared above.
            if err != nil {
                return nil, err
            }
            return svcinit.ServiceAsTask(NewHTTPServiceImpl(initData.db)), nil
        },
        svcinit.WithDataParentFromSetup[svcinit.Task](true),
        svcinit.WithDataName[svcinit.Task]("HTTP service"),
    ))

    //
    // initialize and start the messaging service.
    //
    sinit.AddTask(StageService, svcinit.BuildDataTask[MessagingService](
        func(ctx context.Context) (MessagingService, error) {
            initData, err := initTask.Value() // get the init value from the future declared above.
            if err != nil {
                return nil, err
            }
            return NewMessagingServiceImpl(logger, initData.db), nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service MessagingService) error {
            // service is the object returned from the setup step function above.
            return service.Start(ctx)
        }),
        svcinit.WithDataStop(func(ctx context.Context, service MessagingService) error {
            // service is the object returned from the setup step function above.
            err := service.Stop(ctx)
            if err != nil {
                return err
            }

            // the stop method of the TCP listener do not wait until the connection is shutdown to return.
            // Using the [svcinit.WithStartStepManager] task option, we have access to an interface from the context
            // that we can use to cancel the "start" step context and/or wait for its completion.
            ssm := svcinit.StartStepManagerFromContext(ctx)

            // we could also cancel the context of the "start" step manually. As the Go TCP listener don't have
            // context cancellation, it wouldn't do anything in this case.
            // Note that the [svcinit.StartStepManager] context cancellation is not the same as the main/root context
            // cancellation, this is a context exclusive for this interaction.
            // // ssm.ContextCancel(context.Canceled)

            select {
            case <-ctx.Done():
            case <-ssm.Finished():
                // will be signaled when the "start" step of this task ends.
                // "ssm.FinishedErr()" will contain the error that was returned from it.
            }
            return nil
        }),
        svcinit.WithDataName[MessagingService]("Messaging service"),
    ), svcinit.WithStartStepManager())

    //
    // Signal handling
    //
    sinit.AddTask(StageManagement, svcinit.SignalTask(os.Interrupt, syscall.SIGINT, syscall.SIGTERM))

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

    //
    // start execution
    //
    return sinit.Run(ctx)
}
  • Start management stage:
    • run the setup step of these tasks in parallel and wait for the completion of all of them:
      • telemetry
      • health
    • run the start step of these tasks in parallel but DON'T wait for their completion. They are expected to block until some condition makes then exit.
      • health
      • timeout - (waits 100ms and exits, a debugging tool)
      • signals - (waits until an OS signal is received)
  • Start initialize stage:
    • run the setup step of these tasks in parallel and wait for the completion of all of them:
      • init data - opens the DB connection.
  • Start ready stage:
    • run the setup step of these tasks in parallel and wait for the completion of all of them:
      • health: started probe - signals the startup and readiness probe that the service is started.
  • Start service stage:
    • run the setup step of these tasks in parallel and wait for the completion of all of them:
      • HTTP service
      • Messaging service
    • run the start step of these tasks in parallel but DON'T wait for their completion. They are expected to block until some condition makes then exit.
      • HTTP service
      • Messaging service
  • Wait until the start step of any task returns (with an error or nil).
  • The first start step to return in this example will be timeout, with the error timed out.
  • Cancel the context sent to all start steps which have the WithCancelContext(true) option set, using this timed out error that was returned (in this example, only timeout and signals).
  • A context based on the root context (NOT the one sent to the tasks, that was just cancelled) with a deadline of 20 seconds, is created and will be sent to all stop and teardown steps.
  • Stop service stage:
    • run the stop step of these tasks in parallel and wait for the completion of all of them:
      • HTTP service
      • Messaging service
      • telemetry: flush - flushes the pending telemetry to avoid losing it in case the service is killed.
      • health: terminating probe - signals the readiness probe that the service is terminating.
  • Stop management stage:
    • run the stop step of these tasks in parallel and wait for the completion of all of them:
      • health service
  • Wait until the start step of ALL tasks return, or the shutdown deadline ends.
  • Teardown initialize stage:
    • run the teardown step of these tasks in parallel and wait for the completion of all of them:
      • init data - closes the DB connection.
  • Teardown management stage:
    • run the teardown step of these tasks in parallel and wait for the completion of all of them:
      • telemetry
  • The Run method will return the error timed out.

Real world example - Kubernetes

The github.com/rrgmc/svcinit/v3/k8sinit package contains a Kubernetes service initialization pattern, which is an abstraction of same thing the above real world example does.

Full example source code in the k8sinit/sample folder.

Here is the implementation of the same service above using this package:

import (
    "context"
    "database/sql"
    "fmt"
    "os"

    "github.com/rrgmc/svcinit/v3"
    "github.com/rrgmc/svcinit/v3/health_http"
    "github.com/rrgmc/svcinit/v3/k8sinit"
)

//
// Health helper
//

type HealthHelper interface {
    AddDBHealth(db *sql.DB) // add the DB connection to be checked in the readiness probe
}

//
// HTTP webservice
//

type HTTPService interface {
    svcinit.Service // has "Start(ctx) error" and "Stop(ctx) error" methods.
}

//
// Messaging service
//
// Simulates a messaging service receiving and processing messages.
// This specific sample uses a TCP listener for the simulation.
//

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

func main() {
    ctx := context.Background()
    if err := run(ctx); err != nil {
        fmt.Println(err)
    }
}

func run(ctx context.Context) error {
    logger := defaultLogger(os.Stdout)

    sinit, err := k8sinit.New(
        k8sinit.WithLogger(defaultLogger(os.Stdout)),
    )
    if err != nil {
        return err
    }

    //
    // OpenTelemetry
    //

    // initialize and close OpenTelemetry.
    sinit.SetTelemetryTask(svcinit.BuildTask(
        svcinit.WithSetup(func(ctx context.Context) error {
            // TODO: OpenTelemetry initialization
            return nil
        }),
        svcinit.WithTeardown(func(ctx context.Context) error {
            // TODO: OpenTelemetry closing/flushing
            return nil
        }),
        svcinit.WithName(k8sinit.TaskNameTelemetry),
    ))
    // handle flushing metrics when service begins shutdown.
    sinit.SetTelemetryHandler(k8sinit.BuildTelemetryHandler(
        k8sinit.WithTelemetryHandlerFlushTelemetry(func(ctx context.Context) error {
            // TODO: flush metrics
            return nil
        }),
    ))

    //
    // Health service
    //

    // healthHelper is created in advance because it supports setting a DB instance for the readiness probe to use.
    // Otherwise, [health_http.WithProbeHandler] would not need to be added, a default implementation would be used.
    // It also allows customization of the probe HTTP responses.
    healthHelper := NewHealthHelperImpl()

    // set a health handler and task. [health_http.Server] supports both using the same object.
    healthTask := health_http.NewServer(
        health_http.WithStartupProbe(true), // fails startup and readiness probes until service is started.
        health_http.WithProbeHandler(healthHelper),
        health_http.WithServerTaskName(k8sinit.TaskNameHealth),
    )
    sinit.SetHealthTask(healthTask)
    sinit.SetHealthHandler(healthTask)

    //
    // initialize data to be used by the service, like database and cache connections.
    // A TaskFuture is a Task and a Future at the same time, where the task resolves the future.
    // Following tasks can wait on this future to get the initialized data.
    //
    type initTaskData struct {
        db *sql.DB
    }
    initTask := svcinit.NewTaskFuture[*initTaskData](
        func(ctx context.Context) (data *initTaskData, err error) {
            data = &initTaskData{}

            logger.InfoContext(ctx, "connecting to database")
            // ret.db, err = sql.Open("pgx", "dburl")
            data.db = &sql.DB{}
            if err != nil {
                return nil, err
            }

            // send the initialized DB connection to the health service to be used by the readiness probe.
            healthHelper.AddDBHealth(data.db)

            logger.InfoContext(ctx, "data initialization finished")
            return
        },
        svcinit.WithDataTeardown(func(ctx context.Context, data *initTaskData) error {
            logger.InfoContext(ctx, "closing database connection")
            // return data.db.Close()
            return nil
        }),
        svcinit.WithDataName[*initTaskData]("init data"),
    )
    sinit.AddTask(k8sinit.StageInitialize, initTask)

    //
    // initialize and start the HTTP service.
    //
    sinit.AddTask(k8sinit.StageService, svcinit.BuildDataTask[svcinit.Task](
        func(ctx context.Context) (svcinit.Task, error) {
            // using the WithDataParentFromSetup parameter, returning a [svcinit.Task] from this "setup" step
            // sets it as the parent task, and all of its steps are added to this one.
            // This makes Start and Stop be called automatically.
            initData, err := initTask.Value() // get the init value from the future declared above.
            if err != nil {
                return nil, err
            }
            return svcinit.ServiceAsTask(NewHTTPServiceImpl(initData.db)), nil
        },
        svcinit.WithDataParentFromSetup[svcinit.Task](true),
        svcinit.WithDataName[svcinit.Task]("HTTP service"),
    ))

    //
    // initialize and start the messaging service.
    //
    sinit.AddTask(k8sinit.StageService, svcinit.BuildDataTask[MessagingService](
        func(ctx context.Context) (MessagingService, error) {
            initData, err := initTask.Value() // get the init value from the future declared above.
            if err != nil {
                return nil, err
            }
            return NewMessagingServiceImpl(logger, initData.db), nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service MessagingService) error {
            // service is the object returned from the setup step function above.
            return service.Start(ctx)
        }),
        svcinit.WithDataStop(func(ctx context.Context, service MessagingService) error {
            // service is the object returned from the setup step function above.
            err := service.Stop(ctx)
            if err != nil {
                return err
            }

            // the stop method of the TCP listener do not wait until the connection is shutdown to return.
            // Using the [svcinit.WithStartStepManager] task option, we have access to an interface from the context
            // that we can use to cancel the "start" step context and/or wait for its completion.
            ssm := svcinit.StartStepManagerFromContext(ctx)

            // we could also cancel the context of the "start" step manually. As the Go TCP listener don't have
            // context cancellation, it wouldn't do anything in this case.
            // Note that the [svcinit.StartStepManager] context cancellation is not the same as the main/root context
            // cancellation, this is a context exclusive for this interaction.
            // // ssm.ContextCancel(context.Canceled)

            select {
            case <-ctx.Done():
            case <-ssm.Finished():
                // will be signaled when the "start" step of this task ends.
                // "ssm.FinishedErr()" will contain the error that was returned from it.
            }
            return nil
        }),
        svcinit.WithDataName[MessagingService]("Messaging service"),
    ), svcinit.WithStartStepManager())

    // //
    // // debug step: sleep 100ms and shutdown.
    // //
    // sinit.AddTask(k8sinit.StageManagement, svcinit.TimeoutTask(100*time.Millisecond,
    // 	svcinit.WithTimeoutTaskError(errors.New("timed out"))))

    //
    // start execution
    //
    return sinit.Run(ctx)
}
Using same handler for health and HTTP service

This is an example of using the same HTTP server for both health and the HTTP service.

import (
    "context"
    "net/http"
    "os"

    "github.com/rrgmc/svcinit/v3"
    "github.com/rrgmc/svcinit/v3/health_http"
    "github.com/rrgmc/svcinit/v3/k8sinit"
)

// runSingleHTTP uses the same HTTP server for both health and the service itself.
func runSingleHTTP(ctx context.Context) error {
    // handler for the health endpoints
    healthHandler := health_http.NewHandler(
        health_http.WithStartupProbe(true), // fails startup and readiness probes until service is started.
    )
    // HTTP handler wrapper which handles the health requests, and forward the other to the real handler.
    // The real handler will be set in a following step.
    httpHandlerWrapper := health_http.NewHTTPWrapper(healthHandler)

    sinit, err := k8sinit.New(
        k8sinit.WithLogger(defaultLogger(os.Stdout)),
    )
    if err != nil {
        return err
    }

    // sets the health handler, which will handle the ServiceStarted and ServiceTerminating calls.
    sinit.SetHealthHandler(healthHandler)

    // start the main HTTP server as the health task, so it starts at the right time.
    sinit.SetHealthTask(svcinit.BuildDataTask[*http.Server](
        func(ctx context.Context) (*http.Server, error) {
            mux := http.NewServeMux()
            healthHandler.Register(mux)
            return &http.Server{
                Handler: httpHandlerWrapper,
                Addr:    ":8080",
            }, nil
        },
        svcinit.WithDataStart(func(ctx context.Context, service *http.Server) error {
            return service.ListenAndServe()
        }),
        svcinit.WithDataStop(func(ctx context.Context, service *http.Server) error {
            return service.Shutdown(ctx)
        }),
        svcinit.WithDataName[*http.Server](k8sinit.TaskNameHealth),
    ))

    //
    // initialize and start the HTTP service.
    // It will set the real HTTP handler to the health handler wrapped one. It will handle the health endpoints,
    // and forward the other requests to this handler.
    //
    sinit.AddTask(k8sinit.StageService, svcinit.BuildTask(
        svcinit.WithSetup(func(ctx context.Context) error {
            mux := http.NewServeMux()
            mux.Handle("GET /test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
                _, _ = w.Write([]byte("Hello World, test"))
            }))
            mux.Handle("GET /", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
                _, _ = w.Write([]byte("Hello World"))
            }))
            // set the real HTTP handler mux.
            httpHandlerWrapper.SetHTTPHandler(mux)
            return nil
        }),
        svcinit.WithName("HTTP service"),
    ))

    return sinit.Run(ctx)
}

Author

Rangel Reale (rangelreale@gmail.com)

Documentation

Index

Examples

Constants

View Source
const (
	TaskNameSignals = "signals"
	TaskNameTimeout = "timeout"
)
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. If not available, err is guaranteed to be nil.

func GetTaskDescription added in v3.2.0

func GetTaskDescription(task Task) string

GetTaskDescription returns the task description, be it the String method, a task name, or its variable type.

func GetTaskName added in v3.2.0

func GetTaskName(task Task) string

GetTaskName gets the name of task, or blank if it don't have one.

func LoggerFromContext added in v3.2.0

func LoggerFromContext(ctx context.Context) *slog.Logger

LoggerFromContext gets the default logger from the context.

Types

type BaseOverloadedTask

type BaseOverloadedTask[T Task] struct {
	Task T
}

BaseOverloadedTask wraps a task and forwards all task interface methods. It doesn't implement TaskWithWrapped.

func (*BaseOverloadedTask[T]) String

func (t *BaseOverloadedTask[T]) String() string

func (*BaseOverloadedTask[T]) TaskName added in v3.2.0

func (t *BaseOverloadedTask[T]) TaskName() string

func (*BaseOverloadedTask[T]) TaskOptions

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

func (*BaseOverloadedTask[T]) TaskSteps

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

type BaseWrappedTask

type BaseWrappedTask[T Task] struct {
	*BaseOverloadedTask[T]
}

BaseWrappedTask wraps a task and forwards all task interface methods. It implements TaskWithWrapped.

func NewBaseWrappedTask

func NewBaseWrappedTask[T Task](task T) *BaseWrappedTask[T]

func (*BaseWrappedTask[T]) Run

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

func (*BaseWrappedTask[T]) WrappedTask

func (t *BaseWrappedTask[T]) WrappedTask() Task

type BuildHealthHandlerOption added in v3.7.1

type BuildHealthHandlerOption func(*buildHealthHandler)

func WithHealthHandlerServiceStarted added in v3.7.1

func WithHealthHandlerServiceStarted(f func(context.Context)) BuildHealthHandlerOption

func WithHealthHandlerServiceTerminating added in v3.7.1

func WithHealthHandlerServiceTerminating(f func(context.Context)) BuildHealthHandlerOption

type CallbackStep

type CallbackStep int

CallbackStep defines the callback step (before or after the task step).

const (
	CallbackStepBefore CallbackStep = iota
	CallbackStepAfter
)

func (CallbackStep) String

func (s CallbackStep) String() string

type Future

type Future[T any] interface {
	// Value gets the resolved future value.
	// By default, it waits for the value to be resolved. Use the WithoutFutureWait option to prevent this.
	Value(options ...FutureValueOption) (T, error)
	// Done is a channel that is closed when the future value is resolved.
	Done() <-chan struct{}
}

Future is a proxy for a result that will be resolved in the future.

type FutureResolver

type FutureResolver[T any] interface {
	Future[T]
	// Resolve resolves the Future with the passed value.
	Resolve(value T)
	// ResolveError resolves the Future with the passed error.
	ResolveError(err error)
}

FutureResolver is a resolvable Future.

func NewFuture

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

type FutureValueOption

type FutureValueOption func(*futureValueOptions)

func WithFutureCtx

func WithFutureCtx(ctx context.Context) FutureValueOption

WithFutureCtx adds a context to be checked if WithoutFutureWait is set.

func WithoutFutureWait added in v3.5.0

func WithoutFutureWait() FutureValueOption

WithoutFutureWait prevents [Future.Value] to wait for the future to be resolved. If the future was not resolved yet, ErrNotResolved will be returned.

type HealthHandler added in v3.6.0

type HealthHandler interface {
	ServiceStarted(context.Context)
	ServiceTerminating(ctx context.Context)
}

HealthHandler is a health handler definition that allows implementing probes.

func BuildHealthHandler added in v3.7.1

func BuildHealthHandler(options ...BuildHealthHandlerOption) HealthHandler

BuildHealthHandler builds a HealthHandler from callback functions.

type Manager

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

Manager manages the complete execution lifecycle.

Example
package main

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

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

// healthService implements an HTTP server used to serve health probes.
type healthService struct {
	server *http.Server
}

func newHealthService() *healthService {
	return &healthService{
		server: &http.Server{
			Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(http.StatusOK)
			}),
			Addr: ":8081",
		},
	}
}

func (s *healthService) Start(ctx context.Context) error {
	s.server.BaseContext = func(net.Listener) context.Context {
		return ctx
	}
	return s.server.ListenAndServe()
}

func (s *healthService) Stop(ctx context.Context) error {
	return s.server.Shutdown(ctx)
}

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

	sinit, err := svcinit.New(
		// initialization in 3 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 20-second cancellation during shutdown.
		svcinit.WithShutdownTimeout(20*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", svcinit.BuildDataTask[*healthService](
		// the "BuildDataTask" setup callback returns an instance that is sent to all following steps.
		func(ctx context.Context) (*healthService, error) {
			return newHealthService(), nil
		},
		svcinit.WithDataStart(func(ctx context.Context, service *healthService) error {
			return service.Start(ctx)
		}),
		svcinit.WithDataStop(func(ctx context.Context, service *healthService) error {
			return service.Stop(ctx)
		}),
	))

	// add a task to start the core HTTP server.
	sinit.AddTask("service", svcinit.BuildDataTask[*http.Server](
		func(ctx context.Context) (*http.Server, error) {
			// initialize the service in the setup step.
			// as this may take some time in bigger services, initializing here allows other tasks to initialize
			// at the same time.
			server := &http.Server{
				Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(http.StatusOK)
				}),
				Addr: ":8080",
			}
			return server, nil
		},
		svcinit.WithDataStart(func(ctx context.Context, service *http.Server) error {
			service.BaseContext = func(net.Listener) context.Context {
				return ctx
			}
			return service.ListenAndServe()
		}),
		// stop the service. By default, the context is NOT cancelled, this method must arrange for the start
		// function to end.
		svcinit.WithDataStop(func(ctx context.Context, service *http.Server) error {
			return service.Shutdown(ctx)
		}),
	))

	// 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

func (m *Manager) AddInitError(err error)

func (*Manager) AddService

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

AddService add a Service to be executed at the passed stage.

func (*Manager) AddTask

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

AddTask add a Task to be executed at the passed stage.

func (*Manager) AddTaskFunc

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

AddTaskFunc add a Task to be executed at the passed stage.

func (*Manager) IsRunning

func (m *Manager) IsRunning() bool

func (*Manager) Run

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

Run executes the initialization and returns the error of the first task stop step that returns.

func (*Manager) RunWithStopErrors

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

RunWithStopErrors executes the initialization and returns the error of the first task stop step that returns, and also any errors happening during shutdown in a wrapped 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

Stages returns the stages configured for execution.

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 WithAfterRun added in v3.6.0

func WithAfterRun(afterRun func(ctx context.Context, cause error, stopErr error) error) Option

WithAfterRun adds a callback to be executed after all stages run. The returned error will be the cause returned from Manager.Run. Return the same cause parameter as error to keep it.

func WithBeforeRun added in v3.6.0

func WithBeforeRun(beforeRun func(ctx context.Context) (context.Context, error)) Option

WithBeforeRun adds a callback to be executed before stages are run. Return a changed context, or the same one received. Any error that is returned will abort the Manager.Run execution with the passed error.

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

WithManagerCallback adds a manager callback. Multiple callbacks may be added.

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

WithStages sets the initialization stages. The default value is "[StageDEFAULT]".

func WithTaskCallback

func WithTaskCallback(callbacks ...TaskCallback) Option

WithTaskCallback adds a task callback. Multiple callbacks may be added.

func WithTaskErrorHandler added in v3.6.0

func WithTaskErrorHandler(handler TaskErrorHandler) Option

WithTaskErrorHandler sets a callback that can change the error returned from a task. This can be used for example to ignore errors that are not errors, like [http.ErrServerClosed].

func WithTeardownTimeout added in v3.3.2

func WithTeardownTimeout(teardownTimeout time.Duration) Option

WithTeardownTimeout sets a teardown timeout. If less then or equal to 0, makes it continue using the timeout set for shutdown instead of creating a new one. The default is 0.

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 ServiceName added in v3.3.2

type ServiceName interface {
	TaskName
}

ServiceName allows services to have a name.

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 ServiceWithSetup

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

ServiceWithSetup is a Service which has a Setup step.

type ServiceWithTeardown added in v3.3.2

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

ServiceWithTeardown is a Service which has a Teardown 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 // cancel the "start" step context. Returns whether the cancellation was possible.
	Finished() <-chan struct{}      // channel that will be closed once the "start" step finishes.
	FinishedErr() error             // the error returned from the start step.
	CanContextCancel() bool         // returns whether the "start" step context can be called.
	CanFinished() bool              // returns whether the Finished channel can be checked. If false, Finished will return a nil channel.
}

StartStepManager allows the "stop" step to cancel the start step and/or wait for it to finish.

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

Step defines the possible task runnable steps.

const (
	StepSetup Step = iota
	StepStart
	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 BuildTask

func BuildTask(options ...TaskBuildOption) Task

BuildTask creates a task from callback functions.

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

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

type TaskBuildDataOption

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

func WithDataName added in v3.2.0

func WithDataName[T any](name string) TaskBuildDataOption[T]

WithDataName sets the task name.

func WithDataParent

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

WithDataParent sets a parent task. Any step not set in the built task will be forwarded to it.

func WithDataParentFromSetup

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

WithDataParentFromSetup sets a parent task from the result of the "setup" task. If this value doesn't implement Task, an initialization error will be issued.

func WithDataStart

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

WithDataStart sets a callback for the "start" step.

func WithDataStop

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

WithDataStop sets a callback for the "stop" step.

func WithDataTaskOptions

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

WithDataTaskOptions sets default task options for the TaskOption interface.

func WithDataTeardown

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

WithDataTeardown sets a callback for the "teardown" step.

type TaskBuildDataSetupFunc

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 WithName added in v3.2.0

func WithName(name string) TaskBuildOption

WithName sets the task name.

func WithParent

func WithParent(parent Task) TaskBuildOption

WithParent sets a parent task. Any step not set in the built task will be forwarded to it.

func WithSetup

func WithSetup(f TaskBuildFunc) TaskBuildOption

WithSetup sets a callback for the "setup" step.

func WithStart

func WithStart(f TaskBuildFunc) TaskBuildOption

WithStart sets a callback for the "start" step.

func WithStep

func WithStep(step Step, f TaskBuildFunc) TaskBuildOption

WithStep sets the callback for a step.

func WithStop

func WithStop(f TaskBuildFunc) TaskBuildOption

WithStop sets a callback for the "stop" step.

func WithTaskOptions

func WithTaskOptions(options ...TaskInstanceOption) TaskBuildOption

WithTaskOptions sets default task options for the TaskOption interface.

func WithTeardown

func WithTeardown(f TaskBuildFunc) TaskBuildOption

WithTeardown sets a callback for the "teardown" step.

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 TaskErrorHandler added in v3.6.0

type TaskErrorHandler func(ctx context.Context, task Task, step Step, err error) 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

func (t TaskFunc) String() string

type TaskFuture

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

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

type TaskHandler

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

TaskHandler should call task.Run(ctx, step) and return its error. It can do any processing it needs before and after the call. WARNING: not calling the method, or calling it for any other step, will break the promise of never calling the steps out of order or multiple times.

type TaskInstanceOption

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

type TaskName added in v3.2.0

type TaskName interface {
	TaskName() string
}

TaskName allows tasks to have a name.

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

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

func (t *TaskSignalTask) String() string

func (*TaskSignalTask) TaskName added in v3.2.0

func (t *TaskSignalTask) TaskName() 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 returns 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

func (t *TaskTimeoutTask) String() string

func (*TaskTimeoutTask) TaskName added in v3.2.0

func (t *TaskTimeoutTask) TaskName() 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 TaskWithData added in v3.7.1

type TaskWithData[T any] interface {
	Task
	TaskData() (T, error)
}

func BuildDataTask

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

BuildDataTask creates a task from callback functions, where some data is created in the "setup" step and passed to all other steps.

type TaskWithInitError

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 WithWrapName added in v3.2.0

func WithWrapName(name string) WrapTaskOption

WithWrapName sets the task name.

func WithWrapTaskHandler

func WithWrapTaskHandler(handler TaskHandler) WrapTaskOption

WithWrapTaskHandler sets an optional handler for the task.

Directories

Path Synopsis
sample command

Jump to

Keyboard shortcuts

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