From 5d39ff5e480ca37845f67e0a457e06b219957ed0 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Oct 2025 03:17:30 +0200 Subject: [PATCH 1/5] docker backend: log containerWait return values --- pipeline/backend/docker/docker.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index 4457ad0a8f4..e3b4cad858c 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -248,14 +248,17 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str } func (e *docker) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { - log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name) + log := log.Logger.With().Str("taskUUID", taskUUID).Str("stepUUID", step.UUID).Logger() + log.Trace().Msgf("wait for step %s", step.Name) containerName := toContainerName(step) wait, errC := e.client.ContainerWait(ctx, containerName, "") select { - case <-wait: - case <-errC: + case resp := <-wait: + log.Trace().Msgf("ContainerWait returned with resp: %v", resp) + case err := <-errC: + log.Trace().Msgf("ContainerWait returned with err: %v", err) } info, err := e.client.ContainerInspect(ctx, containerName) From a317e4e22353d246a12c0d5f6d06decfc80b6b68 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Oct 2025 03:22:45 +0200 Subject: [PATCH 2/5] Document pipeline backend engine interface precisely --- pipeline/backend/types/backend.go | 128 ++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 15 deletions(-) diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index 4687018f86d..f9faa40a6ac 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package types defines the Backend interface and related types for +// executing Woodpecker CI workflows across different runtime environments. package types import ( @@ -21,38 +23,134 @@ import ( "github.com/urfave/cli/v3" ) -// Backend defines a container orchestration backend and is used -// to create and manage container resources. +// Backend defines the mechanism for orchestrating workflows and their steps. +// +// A Backend instance may be created multiple times per agent, depending on the +// configured parallel workflow capacity. Each instance handles one workflow at +// a time, but multiple steps within that workflow may execute concurrently. +// +// Thread Safety and Isolation: +// +// - Each workflow must have a unique taskUUID +// - Backend implementations must use taskUUID to isolate workflow resources +// - Expect to have multiple Backend instances running on the same host simultaneously +// - Workflow functions affect only one workflow +// - Step functions must be safe to call concurrently for different steps within an workflow +// +// Intended execution flow: +// +// 1. Initialization (once per backend instance): +// - Name() returns backend identifier +// - IsAvailable() checks environment compatibility +// - Flags() registers configuration options +// - Load() initializes an backend instance +// +// 2. Workflow setup (once per workflow): +// - SetupWorkflow() creates isolated environment +// +// 3. Step execution (once per step, may run concurrently): +// - StartStep() launches the step +// - TailStep() streams logs (async, in background) +// - WaitStep() blocks until completion +// - DestroyStep() cleans up step resources +// +// 4. Workflow cleanup (once per workflow): +// - DestroyWorkflow() removes workflow environment type Backend interface { - // Name returns the name of the backend. + // Name returns the unique identifier of the backend implementation. + // Examples: "docker", "kubernetes", "local", "dummy" Name() string - // IsAvailable check if the backend is available. + // IsAvailable checks if the backend is available and can be used in the + // current environment. For example, a Docker backend would check if the + // Docker daemon is accessible. IsAvailable(ctx context.Context) bool - // Flags return the configuration flags of the backend. + // Flags returns the configuration flags specific to this backend. + // Are used to configure backend-specific behavior + // (e.g., Docker socket path, Kubernetes namespace). Flags() []cli.Flag - // Load loads the backend engine. + // Load initializes the backend engine and returns metadata about its + // capabilities and configuration. + // This is called once per backend instance after flags are parsed. Load(ctx context.Context) (*BackendInfo, error) - // SetupWorkflow sets up the workflow environment. + // SetupWorkflow prepares the execution environment for a new workflow. + // This is called exactly once per workflow, before any steps are started. + // The taskUUID uniquely identifies this workflow and must be used to + // isolate this workflow's resources from other concurrent workflows on + // the same backend instance or host. + // + // Implementations should: + // - Create isolated workspaces, networks, or namespaces + // - Initialize shared volumes or storage + // - Ensure the setup doesn't interfere with other running workflows + // + // Note: Only one workflow is executed at a time per Backend instance, + // but multiple Backend instances may run on the same host. SetupWorkflow(ctx context.Context, conf *Config, taskUUID string) error - // StartStep starts the workflow step. + // StartStep set up and begins execution of a workflow step. + // This may be called concurrently for multiple but unique steps within + // the same workflow, depending on the dependency graph. + // + // Implementations should: + // - Start the step's container/process/pod + // - Use taskUUID to associate the step with its workflow + // - Ensure steps can run independently without blocking each other + // - Handle different step types (commands, plugins, services, cache, clone) + // + // The step's UUID uniquely identifies it within the workflow. StartStep(ctx context.Context, step *Step, taskUUID string) error - // WaitStep waits for the workflow step to complete and returns - // the completion results. - WaitStep(ctx context.Context, step *Step, taskUUID string) (*State, error) - - // TailStep tails the workflow step logs. + // TailStep streams the step's logs back to the caller. + // This is started in a background goroutine immediately after + // StartStep, before WaitStep is called. + // + // The returned io.ReadCloser should: + // - Stream logs as they are produced by the step + // - Remain open until the step completes or is destroyed + // + // The reader will be closed by the caller when no longer needed, which + // may be after WaitStep returns or during DestroyStep. TailStep(ctx context.Context, step *Step, taskUUID string) (io.ReadCloser, error) - // DestroyStep destroys the workflow step. + // WaitStep blocks until the step completes and returns its final state. + // This is called after StartStep and TailStep while TailStep is + // streaming logs in the background. + // + // Returns: + // - State.ExitCode: The step's exit code (0 for success, non-zero for failure) + // - State.Error: Any error that occurred during step execution + // - State.Exited: Timestamp when the step completed + // + // The TailStep reader may be closed either when WaitStep completes or + // during DestroyStep - implementations should handle both cases. + WaitStep(ctx context.Context, step *Step, taskUUID string) (*State, error) + + // DestroyStep cleans up resources associated with a step. + // This is called after WaitStep completes, or if the workflow is cancelled. + // + // Implementations should: + // - Stop the step if still running + // - Clean up step-specific resources (containers, processes) + // - Close any open log streams + // - Not affect other steps in the same workflow + // + // Must be safe to call even if StartStep failed or the step was never started. DestroyStep(ctx context.Context, step *Step, taskUUID string) error - // DestroyWorkflow destroys the workflow environment. + // DestroyWorkflow cleans up all workflow-level resources. + // This is called after all steps have been destroyed (via DestroyStep). + // + // Implementations should: + // - Remove workflow-specific workspaces, networks, or namespaces + // - Clean up shared volumes or storage + // - Ensure complete cleanup so the taskUUID can be reused later + // - Not affect other workflows that may be running on the host + // + // Must be safe to call even if SetupWorkflow failed. DestroyWorkflow(ctx context.Context, conf *Config, taskUUID string) error } From 3b3c2c22b2c6699ae95ebc35e485e876eb2e1c60 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Oct 2025 04:45:58 +0200 Subject: [PATCH 3/5] misspells --- pipeline/backend/types/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index f9faa40a6ac..488f84c8b92 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -130,7 +130,7 @@ type Backend interface { WaitStep(ctx context.Context, step *Step, taskUUID string) (*State, error) // DestroyStep cleans up resources associated with a step. - // This is called after WaitStep completes, or if the workflow is cancelled. + // This is called after WaitStep completes, or if the workflow is canceled. // // Implementations should: // - Stop the step if still running From edcb401ad67121f17aee04f6d26b755ceee67214 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Oct 2025 06:49:07 +0200 Subject: [PATCH 4/5] Update pipeline/backend/types/backend.go --- pipeline/backend/types/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index 488f84c8b92..5e66c63e55d 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -142,9 +142,9 @@ type Backend interface { DestroyStep(ctx context.Context, step *Step, taskUUID string) error // DestroyWorkflow cleans up all workflow-level resources. - // This is called after all steps have been destroyed (via DestroyStep). // // Implementations should: + // - Destroy steps still running in the background (detached steps and services) // - Remove workflow-specific workspaces, networks, or namespaces // - Clean up shared volumes or storage // - Ensure complete cleanup so the taskUUID can be reused later From 90f704f31c8de6ed3d351aba2ed2e8b7c9a69af9 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Oct 2025 12:32:04 +0200 Subject: [PATCH 5/5] update docs --- pipeline/backend/types/backend.go | 48 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index 5e66c63e55d..229174c1dc9 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -25,17 +25,18 @@ import ( // Backend defines the mechanism for orchestrating workflows and their steps. // -// A Backend instance may be created multiple times per agent, depending on the -// configured parallel workflow capacity. Each instance handles one workflow at -// a time, but multiple steps within that workflow may execute concurrently. +// A Backend instance is created once per agent and must handle multiple +// workflows concurrently, depending on the configured parallel workflow +// capacity. Each workflow may have multiple steps executing concurrently. // // Thread Safety and Isolation: // // - Each workflow must have a unique taskUUID // - Backend implementations must use taskUUID to isolate workflow resources -// - Expect to have multiple Backend instances running on the same host simultaneously -// - Workflow functions affect only one workflow -// - Step functions must be safe to call concurrently for different steps within an workflow +// - A single Backend instance must safely handle multiple concurrent workflows +// - Workflow functions may be called concurrently for different workflows +// - Step functions must be safe to call concurrently for different steps, +// even across different workflows // // Intended execution flow: // @@ -43,10 +44,10 @@ import ( // - Name() returns backend identifier // - IsAvailable() checks environment compatibility // - Flags() registers configuration options -// - Load() initializes an backend instance +// - Load() initializes the backend instance // -// 2. Workflow setup (once per workflow): -// - SetupWorkflow() creates isolated environment +// 2. Workflow setup (once per workflow, may be called concurrently): +// - SetupWorkflow() creates isolated environment for the workflow // // 3. Step execution (once per step, may run concurrently): // - StartStep() launches the step @@ -54,7 +55,7 @@ import ( // - WaitStep() blocks until completion // - DestroyStep() cleans up step resources // -// 4. Workflow cleanup (once per workflow): +// 4. Workflow cleanup (once per workflow, may be called concurrently): // - DestroyWorkflow() removes workflow environment type Backend interface { // Name returns the unique identifier of the backend implementation. @@ -73,27 +74,28 @@ type Backend interface { // Load initializes the backend engine and returns metadata about its // capabilities and configuration. - // This is called once per backend instance after flags are parsed. + // This is called once after flags are parsed. + // The backend must be ready to handle multiple concurrent workflows + // after Load completes successfully. Load(ctx context.Context) (*BackendInfo, error) // SetupWorkflow prepares the execution environment for a new workflow. // This is called exactly once per workflow, before any steps are started. // The taskUUID uniquely identifies this workflow and must be used to - // isolate this workflow's resources from other concurrent workflows on - // the same backend instance or host. + // isolate this workflow's resources from other concurrent workflows. // // Implementations should: // - Create isolated workspaces, networks, or namespaces // - Initialize shared volumes or storage // - Ensure the setup doesn't interfere with other running workflows // - // Note: Only one workflow is executed at a time per Backend instance, - // but multiple Backend instances may run on the same host. + // This function may be called concurrently for different workflows. + // Implementations must be thread-safe and handle concurrent workflow setup. SetupWorkflow(ctx context.Context, conf *Config, taskUUID string) error // StartStep set up and begins execution of a workflow step. - // This may be called concurrently for multiple but unique steps within - // the same workflow, depending on the dependency graph. + // This may be called concurrently for multiple steps within the same + // workflow, depending on the dependency graph. // // Implementations should: // - Start the step's container/process/pod @@ -102,6 +104,7 @@ type Backend interface { // - Handle different step types (commands, plugins, services, cache, clone) // // The step's UUID uniquely identifies it within the workflow. + // This function must be thread-safe for concurrent calls. StartStep(ctx context.Context, step *Step, taskUUID string) error // TailStep streams the step's logs back to the caller. @@ -114,6 +117,7 @@ type Backend interface { // // The reader will be closed by the caller when no longer needed, which // may be after WaitStep returns or during DestroyStep. + // This function must be thread-safe for concurrent calls. TailStep(ctx context.Context, step *Step, taskUUID string) (io.ReadCloser, error) // WaitStep blocks until the step completes and returns its final state. @@ -127,6 +131,7 @@ type Backend interface { // // The TailStep reader may be closed either when WaitStep completes or // during DestroyStep - implementations should handle both cases. + // This function must be thread-safe for concurrent calls. WaitStep(ctx context.Context, step *Step, taskUUID string) (*State, error) // DestroyStep cleans up resources associated with a step. @@ -136,21 +141,24 @@ type Backend interface { // - Stop the step if still running // - Clean up step-specific resources (containers, processes) // - Close any open log streams - // - Not affect other steps in the same workflow + // - Not affect other steps in the same or other workflows // // Must be safe to call even if StartStep failed or the step was never started. + // This function must be thread-safe for concurrent calls. DestroyStep(ctx context.Context, step *Step, taskUUID string) error // DestroyWorkflow cleans up all workflow-level resources. // // Implementations should: - // - Destroy steps still running in the background (detached steps and services) + // - Destroy steps still running in the background (detached steps and services) // - Remove workflow-specific workspaces, networks, or namespaces // - Clean up shared volumes or storage // - Ensure complete cleanup so the taskUUID can be reused later - // - Not affect other workflows that may be running on the host + // - Not affect other workflows that may be running concurrently // // Must be safe to call even if SetupWorkflow failed. + // This function may be called concurrently for different workflows + // and must be thread-safe. DestroyWorkflow(ctx context.Context, conf *Config, taskUUID string) error }