diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go index f16814f0..0b568b6e 100644 --- a/internal/lsp/cache/session.go +++ b/internal/lsp/cache/session.go @@ -18,6 +18,7 @@ import ( "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/xlog" "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/xcontext" ) type session struct { @@ -64,11 +65,11 @@ func (s *session) Cache() source.Cache { return s.cache } -func (s *session) NewView(name string, folder span.URI) source.View { +func (s *session) NewView(ctx context.Context, name string, folder span.URI) source.View { index := atomic.AddInt64(&viewIndex, 1) s.viewMu.Lock() defer s.viewMu.Unlock() - ctx := context.Background() + ctx = xcontext.Detach(ctx) backgroundCtx, cancel := context.WithCancel(ctx) v := &view{ session: s, diff --git a/internal/lsp/cmd/check_test.go b/internal/lsp/cmd/check_test.go index dba72c79..629c073e 100644 --- a/internal/lsp/cmd/check_test.go +++ b/internal/lsp/cmd/check_test.go @@ -5,7 +5,6 @@ package cmd_test import ( - "context" "fmt" "strings" "testing" @@ -23,7 +22,7 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { fname := uri.Filename() args := []string{"-remote=internal", "check", fname} out := captureStdOut(t, func() { - tool.Main(context.Background(), r.app, args) + tool.Main(r.ctx, r.app, args) }) // parse got into a collection of reports got := map[string]struct{}{} diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index f0b15409..837019da 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -26,6 +26,7 @@ import ( "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/tool" + "golang.org/x/tools/internal/xcontext" ) // Application is the main application as passed to tool.Main @@ -148,7 +149,7 @@ func (app *Application) connect(ctx context.Context) (*connection, error) { return c, nil } connection := newConnection(app) - ctx := context.Background() //TODO:a way of shutting down the internal server + ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server cr, sw, _ := os.Pipe() sr, cw, _ := os.Pipe() var jc *jsonrpc2.Conn diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go index a9b173e7..3dbab9ed 100644 --- a/internal/lsp/cmd/cmd_test.go +++ b/internal/lsp/cmd/cmd_test.go @@ -6,6 +6,7 @@ package cmd_test import ( "bytes" + "context" "io/ioutil" "os" "path/filepath" @@ -24,6 +25,7 @@ type runner struct { exporter packagestest.Exporter data *tests.Data app *cmd.Application + ctx context.Context } func TestCommandLine(t *testing.T) { @@ -38,6 +40,7 @@ func testCommandLine(t *testing.T, exporter packagestest.Exporter) { exporter: exporter, data: data, app: cmd.New(data.Config.Dir, data.Exported.Config.Env), + ctx: tests.Context(t), } tests.Run(t, r, data) } diff --git a/internal/lsp/cmd/definition_test.go b/internal/lsp/cmd/definition_test.go index 48904527..bf1531a6 100644 --- a/internal/lsp/cmd/definition_test.go +++ b/internal/lsp/cmd/definition_test.go @@ -5,7 +5,6 @@ package cmd_test import ( - "context" "fmt" "os" "path/filepath" @@ -56,7 +55,7 @@ func TestDefinitionHelpExample(t *testing.T) { fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)} { args := append(baseArgs, query) got := captureStdOut(t, func() { - tool.Main(context.Background(), cmd.New("", nil), args) + tool.Main(tests.Context(t), cmd.New("", nil), args) }) if !expect.MatchString(got) { t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got) @@ -84,7 +83,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { uri := d.Src.URI() args = append(args, fmt.Sprint(d.Src)) got := captureStdOut(t, func() { - tool.Main(context.Background(), r.app, args) + tool.Main(r.ctx, r.app, args) }) got = normalizePaths(r.data, got) if mode&jsonGoDef != 0 && runtime.GOOS == "windows" { diff --git a/internal/lsp/cmd/format_test.go b/internal/lsp/cmd/format_test.go index 1ff8b866..b486fac1 100644 --- a/internal/lsp/cmd/format_test.go +++ b/internal/lsp/cmd/format_test.go @@ -5,7 +5,6 @@ package cmd_test import ( - "context" "os/exec" "regexp" "strings" @@ -40,7 +39,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { } app := cmd.New(r.data.Config.Dir, r.data.Config.Env) got := captureStdOut(t, func() { - tool.Main(context.Background(), app, append([]string{"-remote=internal", "format"}, args...)) + tool.Main(r.ctx, app, append([]string{"-remote=internal", "format"}, args...)) }) got = normalizePaths(r.data, got) // check the first two lines are the expected file header diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 6c358ac0..b056fe95 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -32,18 +32,20 @@ func TestLSP(t *testing.T) { type runner struct { server *Server data *tests.Data + ctx context.Context } const viewName = "lsp_test" func testLSP(t *testing.T, exporter packagestest.Exporter) { + ctx := tests.Context(t) data := tests.Load(t, exporter, "testdata") defer data.Exported.Cleanup() log := xlog.New(xlog.StdSink{}) cache := cache.New() session := cache.NewSession(log) - view := session.NewView(viewName, span.FileURI(data.Config.Dir)) + view := session.NewView(ctx, viewName, span.FileURI(data.Config.Dir)) view.SetEnv(data.Config.Env) for filename, content := range data.Config.Overlay { session.SetOverlay(span.FileURI(filename), content) @@ -59,6 +61,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { hoverKind: source.SynopsisDocumentation, }, data: data, + ctx: ctx, } tests.Run(t, r, data) } @@ -67,7 +70,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { v := r.server.session.View(viewName) for uri, want := range data { - f, err := v.GetFile(context.Background(), uri) + f, err := v.GetFile(r.ctx, uri) if err != nil { t.Fatalf("no file for %s: %v", f, err) } @@ -75,7 +78,7 @@ func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { if !ok { t.Fatalf("%s is not a Go file: %v", uri, err) } - results, err := source.Diagnostics(context.Background(), v, gof, nil) + results, err := source.Diagnostics(r.ctx, v, gof, nil) if err != nil { t.Fatal(err) } @@ -218,7 +221,7 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests func (r *runner) runCompletion(t *testing.T, src span.Span) *protocol.CompletionList { t.Helper() - list, err := r.server.Completion(context.Background(), &protocol.CompletionParams{ + list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{ TextDocumentPositionParams: protocol.TextDocumentPositionParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(src.URI()), @@ -295,7 +298,6 @@ func summarizeCompletionItems(i int, want []source.CompletionItem, got []protoco } func (r *runner) Format(t *testing.T, data tests.Formats) { - ctx := context.Background() for _, spn := range data { uri := spn.URI() filename := uri.Filename() @@ -305,7 +307,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { return out, nil })) - edits, err := r.server.Formatting(context.Background(), &protocol.DocumentFormattingParams{ + edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(uri), }, @@ -316,7 +318,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { } continue } - _, m, err := getSourceFile(ctx, r.server.session.ViewOf(uri), uri) + _, m, err := getSourceFile(r.ctx, r.server.session.ViewOf(uri), uri) if err != nil { t.Error(err) } @@ -333,7 +335,6 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { } func (r *runner) Import(t *testing.T, data tests.Imports) { - ctx := context.Background() for _, spn := range data { uri := spn.URI() filename := uri.Filename() @@ -343,7 +344,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { return out, nil })) - actions, err := r.server.CodeAction(context.Background(), &protocol.CodeActionParams{ + actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(uri), }, @@ -354,7 +355,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { } continue } - _, m, err := getSourceFile(ctx, r.server.session.ViewOf(uri), uri) + _, m, err := getSourceFile(r.ctx, r.server.session.ViewOf(uri), uri) if err != nil { t.Error(err) } @@ -393,13 +394,13 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { var locs []protocol.Location var hover *protocol.Hover if d.IsType { - locs, err = r.server.TypeDefinition(context.Background(), params) + locs, err = r.server.TypeDefinition(r.ctx, params) } else { - locs, err = r.server.Definition(context.Background(), params) + locs, err = r.server.Definition(r.ctx, params) if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } - hover, err = r.server.Hover(context.Background(), params) + hover, err = r.server.Hover(r.ctx, params) } if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) @@ -446,7 +447,7 @@ func (r *runner) Highlight(t *testing.T, data tests.Highlights) { TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, Position: loc.Range.Start, } - highlights, err := r.server.DocumentHighlight(context.Background(), params) + highlights, err := r.server.DocumentHighlight(r.ctx, params) if err != nil { t.Fatal(err) } @@ -492,7 +493,7 @@ func (r *runner) Reference(t *testing.T, data tests.References) { Position: loc.Range.Start, }, } - got, err := r.server.References(context.Background(), params) + got, err := r.server.References(r.ctx, params) if err != nil { t.Fatalf("failed for %v: %v", src, err) } @@ -509,7 +510,6 @@ func (r *runner) Reference(t *testing.T, data tests.References) { } func (r *runner) Rename(t *testing.T, data tests.Renames) { - ctx := context.Background() for spn, newText := range data { tag := fmt.Sprintf("%s-rename", newText) @@ -524,7 +524,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { t.Fatalf("failed for %v: %v", spn, err) } - workspaceEdits, err := r.server.Rename(ctx, &protocol.RenameParams{ + workspaceEdits, err := r.server.Rename(r.ctx, &protocol.RenameParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(uri), }, @@ -544,7 +544,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { var res []string for uri, edits := range *workspaceEdits.Changes { spnURI := span.URI(uri) - _, m, err := getSourceFile(ctx, r.server.session.ViewOf(span.URI(spnURI)), spnURI) + _, m, err := getSourceFile(r.ctx, r.server.session.ViewOf(span.URI(spnURI)), spnURI) if err != nil { t.Error(err) } @@ -612,7 +612,7 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) { URI: string(uri), }, } - symbols, err := r.server.DocumentSymbol(context.Background(), params) + symbols, err := r.server.DocumentSymbol(r.ctx, params) if err != nil { t.Fatal(err) } @@ -688,7 +688,7 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { if err != nil { t.Fatalf("failed for %v: %v", loc, err) } - gotSignatures, err := r.server.SignatureHelp(context.Background(), &protocol.TextDocumentPositionParams{ + gotSignatures, err := r.server.SignatureHelp(r.ctx, &protocol.TextDocumentPositionParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(spn.URI()), }, @@ -754,7 +754,7 @@ func (r *runner) Link(t *testing.T, data tests.Links) { if err != nil { t.Fatal(err) } - gotLinks, err := r.server.DocumentLink(context.Background(), &protocol.DocumentLinkParams{ + gotLinks, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: protocol.NewURI(uri), }, diff --git a/internal/lsp/protocol/detatch.go b/internal/lsp/protocol/detatch.go deleted file mode 100644 index 9e34b09a..00000000 --- a/internal/lsp/protocol/detatch.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package protocol - -import ( - "context" - "time" -) - -// detatch returns a context that keeps all the values of its parent context -// but detatches from the cancellation and error handling. -func detatchContext(ctx context.Context) context.Context { return detatchedContext{ctx} } - -type detatchedContext struct{ parent context.Context } - -func (v detatchedContext) Deadline() (time.Time, bool) { return time.Time{}, false } -func (v detatchedContext) Done() <-chan struct{} { return nil } -func (v detatchedContext) Err() error { return nil } -func (v detatchedContext) Value(key interface{}) interface{} { return v.parent.Value(key) } diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go index 2802c3ff..4e1a3cc0 100644 --- a/internal/lsp/protocol/protocol.go +++ b/internal/lsp/protocol/protocol.go @@ -10,13 +10,14 @@ import ( "golang.org/x/tools/internal/jsonrpc2" "golang.org/x/tools/internal/lsp/telemetry/trace" "golang.org/x/tools/internal/lsp/xlog" + "golang.org/x/tools/internal/xcontext" ) const defaultMessageBufferSize = 20 const defaultRejectIfOverloaded = false func canceller(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID) { - ctx = detatchContext(ctx) + ctx = xcontext.Detach(ctx) ctx, span := trace.StartSpan(ctx, "protocol.canceller") defer span.End() conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: id}) diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index 78d2e90f..c38e38cc 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -30,9 +30,11 @@ func TestSource(t *testing.T) { type runner struct { view source.View data *tests.Data + ctx context.Context } func testSource(t *testing.T, exporter packagestest.Exporter) { + ctx := tests.Context(t) data := tests.Load(t, exporter, "../testdata") defer data.Exported.Cleanup() @@ -40,8 +42,9 @@ func testSource(t *testing.T, exporter packagestest.Exporter) { cache := cache.New() session := cache.NewSession(log) r := &runner{ - view: session.NewView("source_test", span.FileURI(data.Config.Dir)), + view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir)), data: data, + ctx: ctx, } r.view.SetEnv(data.Config.Env) for filename, content := range data.Config.Overlay { @@ -52,11 +55,11 @@ func testSource(t *testing.T, exporter packagestest.Exporter) { func (r *runner) Diagnostics(t *testing.T, data tests.Diagnostics) { for uri, want := range data { - f, err := r.view.GetFile(context.Background(), uri) + f, err := r.view.GetFile(r.ctx, uri) if err != nil { t.Fatal(err) } - results, err := source.Diagnostics(context.Background(), r.view, f.(source.GoFile), nil) + results, err := source.Diagnostics(r.ctx, r.view, f.(source.GoFile), nil) if err != nil { t.Fatal(err) } @@ -132,7 +135,7 @@ func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnost } func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) { - ctx := context.Background() + ctx := r.ctx for src, itemList := range data { var want []source.CompletionItem for _, pos := range itemList { @@ -289,7 +292,7 @@ func summarizeCompletionItems(i int, want []source.CompletionItem, got []source. } func (r *runner) Format(t *testing.T, data tests.Formats) { - ctx := context.Background() + ctx := r.ctx for _, spn := range data { uri := spn.URI() filename := uri.Filename() @@ -327,7 +330,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) { } func (r *runner) Import(t *testing.T, data tests.Imports) { - ctx := context.Background() + ctx := r.ctx for _, spn := range data { uri := spn.URI() filename := uri.Filename() @@ -365,7 +368,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) { } func (r *runner) Definition(t *testing.T, data tests.Definitions) { - ctx := context.Background() + ctx := r.ctx for _, d := range data { f, err := r.view.GetFile(ctx, d.Src.URI()) if err != nil { @@ -407,7 +410,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { } func (r *runner) Highlight(t *testing.T, data tests.Highlights) { - ctx := context.Background() + ctx := r.ctx for name, locations := range data { src := locations[0] f, err := r.view.GetFile(ctx, src.URI()) @@ -432,7 +435,7 @@ func (r *runner) Highlight(t *testing.T, data tests.Highlights) { } func (r *runner) Reference(t *testing.T, data tests.References) { - ctx := context.Background() + ctx := r.ctx for src, itemList := range data { f, err := r.view.GetFile(ctx, src.URI()) if err != nil { @@ -478,7 +481,7 @@ func (r *runner) Reference(t *testing.T, data tests.References) { } func (r *runner) Rename(t *testing.T, data tests.Renames) { - ctx := context.Background() + ctx := r.ctx for spn, newText := range data { tag := fmt.Sprintf("%s-rename", newText) @@ -489,11 +492,11 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { tok := f.GetToken(ctx) pos := tok.Pos(spn.Start().Offset()) - ident, err := source.Identifier(context.Background(), r.view, f.(source.GoFile), pos) + ident, err := source.Identifier(r.ctx, r.view, f.(source.GoFile), pos) if err != nil { t.Error(err) } - changes, err := ident.Rename(context.Background(), newText) + changes, err := ident.Rename(r.ctx, newText) if err != nil { renamed := string(r.data.Golden(tag, spn.URI().Filename(), func() ([]byte, error) { return []byte(err.Error()), nil @@ -568,7 +571,7 @@ func sortSourceTextEdits(d []source.TextEdit) { } func (r *runner) Symbol(t *testing.T, data tests.Symbols) { - ctx := context.Background() + ctx := r.ctx for uri, expectedSymbols := range data { f, err := r.view.GetFile(ctx, uri) if err != nil { @@ -632,7 +635,7 @@ func summarizeSymbols(i int, want []source.Symbol, got []source.Symbol, reason s } func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { - ctx := context.Background() + ctx := r.ctx for spn, expectedSignature := range data { f, err := r.view.GetFile(ctx, spn.URI()) if err != nil { diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index 62a281a1..2e99f49d 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -129,7 +129,7 @@ type Cache interface { // A session may have many active views at any given time. type Session interface { // NewView creates a new View and returns it. - NewView(name string, folder span.URI) View + NewView(ctx context.Context, name string, folder span.URI) View // Cache returns the cache that created this session. Cache() Cache diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index a1589d82..bfa1bc23 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -127,6 +127,10 @@ type Golden struct { Modified bool } +func Context(t testing.TB) context.Context { + return context.Background() +} + func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { t.Helper() @@ -193,7 +197,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data { // Merge the exported.Config with the view.Config. data.Config = *data.Exported.Config data.Config.Fset = token.NewFileSet() - data.Config.Context = context.Background() + data.Config.Context = Context(nil) data.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { panic("ParseFile should not be called") } diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go index 4c761b48..ddb597ec 100644 --- a/internal/lsp/workspace.go +++ b/internal/lsp/workspace.go @@ -31,6 +31,6 @@ func (s *Server) changeFolders(ctx context.Context, event protocol.WorkspaceFold } func (s *Server) addView(ctx context.Context, name string, uri span.URI) error { - s.session.NewView(name, uri) + s.session.NewView(ctx, name, uri) return nil } diff --git a/internal/memoize/detatch.go b/internal/memoize/detatch.go deleted file mode 100644 index 6112de6e..00000000 --- a/internal/memoize/detatch.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package memoize - -import ( - "context" - "time" -) - -// detatch returns a context that keeps all the values of its parent context -// but detatches from the cancellation and error handling. -func detatchContext(ctx context.Context) context.Context { return detatchedContext{ctx} } - -type detatchedContext struct{ parent context.Context } - -func (v detatchedContext) Deadline() (time.Time, bool) { return time.Time{}, false } -func (v detatchedContext) Done() <-chan struct{} { return nil } -func (v detatchedContext) Err() error { return nil } -func (v detatchedContext) Value(key interface{}) interface{} { return v.parent.Value(key) } diff --git a/internal/memoize/memoize.go b/internal/memoize/memoize.go index 9e0cb4c2..279185ce 100644 --- a/internal/memoize/memoize.go +++ b/internal/memoize/memoize.go @@ -19,6 +19,8 @@ import ( "runtime" "sync" "unsafe" + + "golang.org/x/tools/internal/xcontext" ) // Store binds keys to functions, returning handles that can be used to access @@ -180,7 +182,7 @@ func (e *entry) get(ctx context.Context, f Function) (interface{}, bool) { // Use the background context to avoid canceling the function. // The function cannot be canceled even if the context is canceled // because multiple goroutines may depend on it. - value = f(detatchContext(ctx)) + value = f(xcontext.Detach(ctx)) // The function has completed. Update the value in the entry. e.mu.Lock() diff --git a/internal/xcontext/xcontext.go b/internal/xcontext/xcontext.go new file mode 100644 index 00000000..ff8ed4eb --- /dev/null +++ b/internal/xcontext/xcontext.go @@ -0,0 +1,23 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xcontext is a package to offer the extra functionality we need +// from contexts that is not available from the standard context package. +package xcontext + +import ( + "context" + "time" +) + +// Detach returns a context that keeps all the values of its parent context +// but detaches from the cancellation and error handling. +func Detach(ctx context.Context) context.Context { return detachedContext{ctx} } + +type detachedContext struct{ parent context.Context } + +func (v detachedContext) Deadline() (time.Time, bool) { return time.Time{}, false } +func (v detachedContext) Done() <-chan struct{} { return nil } +func (v detachedContext) Err() error { return nil } +func (v detachedContext) Value(key interface{}) interface{} { return v.parent.Value(key) }