Skip to content

Lifecycle

Choosing a constructor

Turbine exposes five entry points. Pick by answering two questions: who owns the PocketBase app and does the app serve HTTP.

I want to...Use this constructor
Build a PocketBase plugin and let the app drive everything (HTTP server, OnServe, OnTerminate)turbine.Setup(app, cfg)
Quickly stand up a turbine-backed app with HTTP serving and no app-construction boilerplateapp, rt := turbine.NewApp(cfg)
Run workflows from a script, cron job, or background worker, no HTTP serverrt := turbine.NewStandalone(cfg)
Run workflows alongside a PocketBase app I configured myself, but without serving HTTPrt := turbine.SetupStandalone(app, cfg)
Embed turbine into a custom lifecycle where I manage Launch and Shutdown by handrt := turbine.NewRuntime(app, cfg)

Lifecycle ordering. Every constructor returns a runtime in the unlaunched state. The required order is:

  1. Construct the runtime via one of the five entry points
  2. turbine.Register(rt, MyWorkflow), register every workflow you want to run
  3. Start the runtime:
    • For Setup and NewApp: call app.Start(), which fires OnServe and triggers rt.Launch() for you
    • For NewStandalone, SetupStandalone, and NewRuntime: call rt.Launch() yourself
  4. Use the runtime
  5. Shut it down:
    • For Setup and NewApp: OnTerminate calls rt.Shutdown() for you when the app exits
    • For NewStandalone, SetupStandalone, and NewRuntime: call rt.Shutdown() yourself (recommended: defer rt.Shutdown() right after construction)

Register must run before Launch. Calling turbine.Register after the runtime has launched will panic. The construct -> register -> launch ordering above prevents this by design.

Logger defaults. NewStandalone defaults cfg.Logger to a stdout slog handler so script output is visible. Every other constructor leaves cfg.Logger as the caller provided it; when cfg.Logger is nil, workflow and step logs fall back to the PocketBase app.Logger(). PocketBase's own log destinations (the _logs collection, etc.) are independent of cfg.Logger.

Shutdown

Two-phase process:

  1. Drain, stop accepting new work, let running workflows finish naturally
  2. Force, if Config.ShutdownTimeout expires, cancel all remaining workflows
go
rt.Shutdown()

During shutdown:

  • rt.IsDraining() returns true
  • Queue runners stop dequeuing
  • New Run calls return turbine.ErrShuttingDown
  • Running workflows get their context cancelled after the timeout

If the drain phase times out and workflows are force-cancelled, Shutdown logs a Warn and returns. Calling Shutdown before Launch is safe. Drain progress messages are written to stdout, bypassing cfg.Logger and app.Logger() so they're still visible while the app's own logging pipeline is tearing down.

INFO

Shutdown always uses Config.ShutdownTimeout (default 30s). Set it on the Config passed to NewRuntime / Setup to tune the deadline.

Runtime State

MethodReturns
rt.IsLaunched()true after Launch(), false after Shutdown()
rt.IsDraining()true during shutdown drain phase
rt.GetExecutorID()Instance identifier (default: "local")
rt.GetApplicationVersion()App version (default: binary hash)
rt.GetApplicationID()App name
rt.App()The core.App

Introspection

Registered Workflows

go
workflows := rt.RegisteredWorkflows()
for _, wf := range workflows {
    fmt.Printf("%s (triggerable=%v, cron=%s, tags=%v)\n",
        wf.Name, wf.Triggerable, wf.CronSchedule, wf.Tags)
}

Returns RegisteredWorkflow with fields: Name, FQN, Triggerable, CronSchedule, InputSchema, Tags.

Registered Queues

go
queues := rt.Queues()
for _, q := range queues {
    fmt.Printf("queue %s (workers=%v, priority=%v)\n",
        q.Name, q.WorkerConcurrency, q.PriorityEnabled)
}

Workflow Steps

go
steps, err := rt.Steps(workflowID)
for _, s := range steps {
    fmt.Printf("step %d: %s (output=%s)\n", s.FunctionID, s.FunctionName, s.Output)
}

In-progress steps are included in the result with EndedAt == 0. They become complete once the step returns and its result is recorded.

Workflow Status Types

StatusMeaning
PENDINGWorkflow started, running or waiting for recovery
ENQUEUEDWaiting in a queue to be picked up
SUCCESSCompleted successfully
ERRORFailed with an error
CANCELLEDCancelled via rt.Cancel()
MAX_RECOVERY_ATTEMPTS_EXCEEDEDExceeded max recovery retries (dead letter)