From f90596118604deacf8d8feb961f3b409e913f636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=B7=E6=BA=AA?= Date: Mon, 27 Sep 2021 15:58:27 +0800 Subject: [PATCH] feat: add WithoutCancel (#200) * feat: add WithoutCancel * fix: hound complaint * fix: WithoutCancel should panic when parent is nil * fix: tests * fix: naked return is not good for readability * chore: surpress linter Co-authored-by: Trock --- ctxmeta/without_cancel.go | 43 ++++++++++++++++++++++++++++++++++ ctxmeta/without_cancel_test.go | 33 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 ctxmeta/without_cancel.go create mode 100644 ctxmeta/without_cancel_test.go diff --git a/ctxmeta/without_cancel.go b/ctxmeta/without_cancel.go new file mode 100644 index 00000000..146e85a3 --- /dev/null +++ b/ctxmeta/without_cancel.go @@ -0,0 +1,43 @@ +package ctxmeta + +import ( + "context" + "time" +) + +type asyncContext struct { + ctx context.Context +} + +func (a asyncContext) Deadline() (deadline time.Time, ok bool) { + return time.Time{}, false +} + +func (a asyncContext) Done() <-chan struct{} { + return nil +} + +func (a asyncContext) Err() error { + return nil +} + +func (a asyncContext) Value(key interface{}) interface{} { + return a.ctx.Value(key) +} + +// WithoutCancel creates a new context from an existing context and inherits all +// values from the existing context. However if the existing context is +// cancelled, timeouts or passes deadline, the returning context will not be +// affected. This is useful in an async HTTP handler. When the http response is sent, +// the request context will be cancelled. If you still want to access the value from request context (eg. tracing), +// you can use: +// func(request *http.Request, responseWriter http.ResponseWriter) { +// go DoSomeSlowDatabaseOperation(WithoutCancel(request.Context())) +// responseWriter.Write([]byte("ok")) +// } +func WithoutCancel(requestScopeContext context.Context) (valueOnlyContext context.Context) { + if requestScopeContext == nil { + panic("cannot create context from nil parent") + } + return asyncContext{requestScopeContext} +} diff --git a/ctxmeta/without_cancel_test.go b/ctxmeta/without_cancel_test.go new file mode 100644 index 00000000..f1320674 --- /dev/null +++ b/ctxmeta/without_cancel_test.go @@ -0,0 +1,33 @@ +package ctxmeta + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWithoutCancel(t *testing.T) { + key := struct{}{} + ctx := context.WithValue(context.Background(), key, "value") + ctx, cancel := context.WithCancel(ctx) + cancel() + + select { + case <-WithoutCancel(ctx).Done(): + t.Fatal("context is cancelled") + default: + } + + _, dead := WithoutCancel(ctx).Deadline() + assert.False(t, dead) + assert.Nil(t, WithoutCancel(ctx).Err()) + assert.Equal(t, "value", WithoutCancel(ctx).Value(key)) +} + +func TestWithoutCancel_Nil(t *testing.T) { + defer func() { + assert.Equal(t, "cannot create context from nil parent", recover()) + }() + WithoutCancel(nil) //nolint +}