Skip to content

Extends the Function Stage to Handle Panics #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jcmanzo
Copy link

@jcmanzo jcmanzo commented May 12, 2025

Overview

Certain stages, such as pipe.Function take in a client function to run. In the event of a panic, the client code cannot handle any panics that happen in the client function because it is run in a separate goroutine.

This PR introduces a panic handler mechanism with the intent of surfacing the panic to the client so that they can react to it accordingly, if they wish. Currently only the pipe.Function will support this new behavior as it was the most likely contender for causing panics as it allows any client go code to be run.

Implementation

  • Adds a light StagePanicHandlerAware interface that is used to mark stages as capable of receiving a panic handler.
  • Updates pipe.Function with panic recover code. If a panic handler was provided at the pipeline level, it will be called. Otherwise the panic will bubble up as before.
  • Extends pipe.Pipeline with a new WithStagePanicHandler option to allow clients to set a panic handler. The handler is passed into stages that support the StagePanicHandlerAware interface.

Example

p := pipe.New(
  pipe.WithDir(dir),
  pipe.WithStagePanicHandler(func(err error) {
	logger.Error("encountered panic: %v", err)
        // Other recovery logic and/or re-panic
  }),
)

p.Add(
  pipe.Print("hello world"),
  pipe.Function(
	"farewell",
	func(_ context.Context, _ pipe.Env, stdin io.Reader, stdout io.Writer) error {
		// Client function that caused panic
	},
  ),
)

@jcmanzo jcmanzo changed the title Adds panic handler Extends the Function Stage to Handle Panics May 12, 2025
@jcmanzo jcmanzo force-pushed the adds-panic-handler branch 5 times, most recently from 84239e3 to 42898bc Compare May 12, 2025 15:41
@jcmanzo jcmanzo marked this pull request as ready for review May 12, 2025 15:41
@Copilot Copilot AI review requested due to automatic review settings May 12, 2025 15:41
@jcmanzo jcmanzo requested a review from a team as a code owner May 12, 2025 15:41
@jcmanzo jcmanzo requested a review from mhagger May 12, 2025 15:42
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a panic‐handling extension so that client code run by the Function stage can surface panics through a configurable handler.

  • Introduces a StagePanicHandlerAware interface, StagePanicHandler type, and FromPanicValue helper.
  • Adds WithStagePanicHandler option to Pipeline and wires the handler into stages that support it.
  • Implements panic recovery in the Function stage and adds tests for the new handler behavior.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
pipe/pipeline_test.go Added t.Run subtests for a successful function and a panic‐with‐handler case
pipe/pipeline.go Added panicHandler field, WithStagePanicHandler option, and wiring logic
pipe/panic.go Defined StagePanicHandlerAware, StagePanicHandler, and FromPanicValue
pipe/function.go Implemented SetPanicHandler on goStage and panic recovery in Start
Comments suppressed due to low confidence (1)

pipe/function.go:56

  • The code path where no panic handler is provided (nil handler) and a panic should bubble up is not covered by tests; consider adding a test to verify that a panic propagates when no handler is set.
if s.panicHandler == nil {

jcmanzo added 2 commits May 12, 2025 17:35
Stages can be extended to allow panics to be caught
and reported. The panic will still bubble up once
the panic handler has resolved.
Clients set the panic handler at the pipeline level.
The pipeline is then responsible for passing the handler
to stages that support the PanicHandlerAware interface.
@jcmanzo jcmanzo force-pushed the adds-panic-handler branch from 42898bc to 11ad4d2 Compare May 12, 2025 17:35
@jcmanzo jcmanzo requested review from NellWhite and Copilot May 12, 2025 21:07
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Introduces a mechanism to recover and surface panics in the Function stage by allowing clients to register a panic handler at the pipeline level.

  • Add a StagePanicHandlerAware interface and StagePanicHandler type for stages to receive handlers.
  • Extend Pipeline with a WithStagePanicHandler option and propagate the handler to supporting stages.
  • Wrap Function stage execution in a recover block to invoke the handler (or re-panic if none is set).

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
pipe/pipeline_test.go Add subtests for "successful function" and "panic with handler" scenarios
pipe/pipeline.go Store a panic handler on the pipeline and assign it to stages before start
pipe/panic.go Define StagePanicHandlerAware, StagePanicHandler, and FromPanicValue
pipe/function.go Add recover in goStage to invoke the panic handler or re-panic
Comments suppressed due to low confidence (1)

pipe/pipeline_test.go:465

  • [nitpick] Consider adding a test case for a panic without a registered handler to verify that the pipeline will re-panic as expected when no handler is provided.
panicked := make(chan bool, 1)

Comment on lines +466 to +470
p := pipe.New(
pipe.WithDir(dir),
pipe.WithStagePanicHandler(func(err error) {
panicked <- true
assert.Equal(t, "this is a panic", err.Error())
Copy link
Preview

Copilot AI May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid calling testing.T methods or assert functions inside a separate goroutine; instead, capture the panic value via a channel and perform the assertion in the main test goroutine.

Suggested change
p := pipe.New(
pipe.WithDir(dir),
pipe.WithStagePanicHandler(func(err error) {
panicked <- true
assert.Equal(t, "this is a panic", err.Error())
panickedMessage := make(chan string, 1)
p := pipe.New(
pipe.WithDir(dir),
pipe.WithStagePanicHandler(func(err error) {
panicked <- true
panickedMessage <- err.Error()

Copilot uses AI. Check for mistakes.

Comment on lines +282 to +285
if p.panicHandler != nil {
if phs, ok := s.(StagePanicHandlerAware); ok {
phs.SetPanicHandler(p.panicHandler)
}
Copy link
Preview

Copilot AI May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] You can simplify the nested if statements by combining the panic-handler nil check and the type assertion into a single block: if phs, ok := s.(StagePanicHandlerAware); ok && p.panicHandler != nil { ... }.

Suggested change
if p.panicHandler != nil {
if phs, ok := s.(StagePanicHandlerAware); ok {
phs.SetPanicHandler(p.panicHandler)
}
if phs, ok := s.(StagePanicHandlerAware); ok && p.panicHandler != nil {
phs.SetPanicHandler(p.panicHandler)

Copilot uses AI. Check for mistakes.

@@ -179,6 +180,19 @@ func WithEventHandler(handler func(e *Event)) Option {
}
}

// WithStagePanicHandler sets a panic handler for the stages within a pipeline.
Copy link
Preview

Copilot AI May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Update the doc comment to explicitly state that if no handler is provided, a panic in the stage will bubble up, preserving the previous behavior.

Copilot uses AI. Check for mistakes.

Copy link

@NellWhite NellWhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a super reasonable way to implement this to me! It will make us a lot less vulnerable to panics, once we use it in gitrpcd!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants