mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
refactor(webhook): extract async context detach into a helper
This commit is contained in:
32
pkg/modules/webhook/async_context.go
Normal file
32
pkg/modules/webhook/async_context.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// detachAsyncContext detaches ctx from the inbound request lifecycle so the
|
||||||
|
// webhook goroutine survives echo recycling the request, while preserving the
|
||||||
|
// conversion deadline.
|
||||||
|
//
|
||||||
|
// Echo cancels the request context as soon as the synchronous handler returns
|
||||||
|
// [api.ErrAsyncProcess], which would abort the asynchronous work. Replacing the
|
||||||
|
// embedded context severs that cancellation. The returned cancel function
|
||||||
|
// cleans up both the detached context and the original working directory.
|
||||||
|
func detachAsyncContext(ctx *api.Context, cancel context.CancelFunc) context.CancelFunc {
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
detachedCtx, detachedCancel := context.WithDeadline(context.Background(), deadline)
|
||||||
|
ctx.Context = detachedCtx
|
||||||
|
|
||||||
|
originalCancel := cancel
|
||||||
|
return func() {
|
||||||
|
detachedCancel()
|
||||||
|
originalCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if no deadline was set (rare, as newContext enforces it).
|
||||||
|
ctx.Context = context.Background()
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
56
pkg/modules/webhook/async_context_test.go
Normal file
56
pkg/modules/webhook/async_context_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetachAsyncContext_PreservesDeadline(t *testing.T) {
|
||||||
|
deadline := time.Now().Add(2 * time.Hour)
|
||||||
|
reqCtx, reqCancel := context.WithDeadline(context.Background(), deadline)
|
||||||
|
defer reqCancel()
|
||||||
|
|
||||||
|
ctx := &api.Context{Context: reqCtx}
|
||||||
|
cancel := detachAsyncContext(ctx, func() {})
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
got, ok := ctx.Deadline()
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected the detached context to keep a deadline")
|
||||||
|
}
|
||||||
|
if !got.Equal(deadline) {
|
||||||
|
t.Errorf("expected deadline %v, got %v", deadline, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetachAsyncContext_SurvivesRequestCancellation(t *testing.T) {
|
||||||
|
reqCtx, reqCancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Hour))
|
||||||
|
|
||||||
|
ctx := &api.Context{Context: reqCtx}
|
||||||
|
cancel := detachAsyncContext(ctx, func() {})
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Cancelling the inbound request must not abort the detached context.
|
||||||
|
reqCancel()
|
||||||
|
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
t.Errorf("expected the detached context to survive request cancellation, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetachAsyncContext_CancelInvokesOriginal(t *testing.T) {
|
||||||
|
reqCtx, reqCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||||
|
defer reqCancel()
|
||||||
|
|
||||||
|
called := 0
|
||||||
|
ctx := &api.Context{Context: reqCtx}
|
||||||
|
cancel := detachAsyncContext(ctx, func() { called++ })
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
if called != 1 {
|
||||||
|
t.Errorf("expected the original cancel to be invoked once, got %d", called)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -314,27 +314,7 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
|||||||
return c.NoContent(http.StatusNoContent)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
cancel = detachAsyncContext(ctx, cancel)
|
||||||
// Create a new context derived from Background (detached from Request)
|
|
||||||
// but with the same deadline as the original context.
|
|
||||||
detachedCtx, detachedCancel := context.WithDeadline(context.Background(), deadline)
|
|
||||||
|
|
||||||
// Replace the embedded context in the api.Context struct.
|
|
||||||
// The modules downstream will now use this detached context.
|
|
||||||
ctx.Context = detachedCtx
|
|
||||||
|
|
||||||
// We must wrap the cancel function.
|
|
||||||
// 1. detachedCancel() cleans up our new detached context.
|
|
||||||
// 2. originalCancel() (captured from c.Get("cancel")) cleans up the working directory.
|
|
||||||
originalCancel := cancel
|
|
||||||
cancel = func() {
|
|
||||||
detachedCancel()
|
|
||||||
originalCancel()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback if no deadline was set (rare, as newContext enforces it).
|
|
||||||
ctx.Context = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
// As a webhook URL has been given, we handle the request in a
|
// As a webhook URL has been given, we handle the request in a
|
||||||
// goroutine and return immediately.
|
// goroutine and return immediately.
|
||||||
|
|||||||
Reference in New Issue
Block a user