internal/lsp: stop making background contexts everywhere

For all uses inside the lsp we use the detatch logic instead
For tests we build it in the test harness instead
This is in preparation for things on the context becomming important

Change-Id: I7e6910e0d3581b82abbeeb09f9c22a99efb73142
Reviewed-on: https://go-review.googlesource.com/c/tools/+/185677
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index f16814f..0b568b6 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -18,6 +18,7 @@
 	"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 @@
 	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 dba72c7..629c073 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 @@
 		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 f0b1540..837019d 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -26,6 +26,7 @@
 	"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 @@
 			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 a9b173e..3dbab9e 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"context"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -24,6 +25,7 @@
 	exporter packagestest.Exporter
 	data     *tests.Data
 	app      *cmd.Application
+	ctx      context.Context
 }
 
 func TestCommandLine(t *testing.T) {
@@ -38,6 +40,7 @@
 		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 4890452..bf1531a 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 @@
 		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 @@
 			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 1ff8b86..b486fac 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 @@
 			}
 			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 6c358ac..b056fe9 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -32,18 +32,20 @@
 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 @@
 			hoverKind: source.SynopsisDocumentation,
 		},
 		data: data,
+		ctx:  ctx,
 	}
 	tests.Run(t, r, data)
 }
@@ -67,7 +70,7 @@
 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 @@
 		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) 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 (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 @@
 			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 @@
 			}
 			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) Import(t *testing.T, data tests.Imports) {
-	ctx := context.Background()
 	for _, spn := range data {
 		uri := spn.URI()
 		filename := uri.Filename()
@@ -343,7 +344,7 @@
 			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 @@
 			}
 			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 @@
 		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 @@
 			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 @@
 				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) 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 @@
 			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 @@
 		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 @@
 				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 @@
 		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 @@
 		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 9e34b09..0000000
--- 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 2802c3f..4e1a3cc 100644
--- a/internal/lsp/protocol/protocol.go
+++ b/internal/lsp/protocol/protocol.go
@@ -10,13 +10,14 @@
 	"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 78d2e90..c38e38c 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -30,9 +30,11 @@
 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 @@
 	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 (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 (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 (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) 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) 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) 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) 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) 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 @@
 		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 (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 (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 62a281a..2e99f49 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -129,7 +129,7 @@
 // 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 a1589d8..bfa1bc2 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -127,6 +127,10 @@
 	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 @@
 	// 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 4c761b4..ddb597e 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -31,6 +31,6 @@
 }
 
 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 6112de6..0000000
--- 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 9e0cb4c..279185c 100644
--- a/internal/memoize/memoize.go
+++ b/internal/memoize/memoize.go
@@ -19,6 +19,8 @@
 	"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 @@
 			// 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 0000000..ff8ed4e
--- /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) }