internal/lsp: move the debug.Instance onto the Context

This allows us to register a telemetry exporter that works with mulitple active
debug instances.
It also means we don't have to store the debug information in our other objects.

Change-Id: I9a9d5b0407c3352b6eaff80fb2c434ca33f4e397
Reviewed-on: https://go-review.googlesource.com/c/tools/+/221558
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
index 0afcf70..a17b1c1 100644
--- a/gopls/test/gopls_test.go
+++ b/gopls/test/gopls_test.go
@@ -42,9 +42,9 @@
 	}
 	data := tests.Load(t, exporter, testdata)
 	ctx := tests.Context(t)
-	di := debug.NewInstance("", "")
-	cache := cache.New(commandLineOptions, di.State)
-	ss := lsprpc.NewStreamServer(cache, false, di)
+	ctx = debug.WithInstance(ctx, "", "")
+	cache := cache.New(ctx, commandLineOptions)
+	ss := lsprpc.NewStreamServer(cache, false)
 	ts := servertest.NewTCPServer(ctx, ss)
 	for _, data := range data {
 		defer data.Exported.Cleanup()
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index 4b71ebc..75401d7 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -19,19 +19,17 @@
 	"golang.org/x/tools/internal/span"
 )
 
-func New(options func(*source.Options), debugState *debug.State) *Cache {
-	if debugState == nil {
-		debugState = &debug.State{}
-	}
+func New(ctx context.Context, options func(*source.Options)) *Cache {
 	index := atomic.AddInt64(&cacheIndex, 1)
 	c := &Cache{
 		fs:      &nativeFileSystem{},
 		id:      strconv.FormatInt(index, 10),
 		fset:    token.NewFileSet(),
 		options: options,
-		debug:   debugState,
 	}
-	debugState.AddCache(debugCache{c})
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.AddCache(debugCache{c})
+	}
 	return c
 }
 
@@ -40,7 +38,6 @@
 	id      string
 	fset    *token.FileSet
 	options func(*source.Options)
-	debug   *debug.State
 
 	store memoize.Store
 }
@@ -79,7 +76,7 @@
 	}
 }
 
-func (c *Cache) NewSession() *Session {
+func (c *Cache) NewSession(ctx context.Context) *Session {
 	index := atomic.AddInt64(&sessionIndex, 1)
 	s := &Session{
 		cache:    c,
@@ -87,7 +84,9 @@
 		options:  source.DefaultOptions(),
 		overlays: make(map[span.URI]*overlay),
 	}
-	c.debug.AddSession(DebugSession{s})
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.AddSession(DebugSession{s})
+	}
 	return s
 }
 
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index eceb84d..4575d6d 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -11,6 +11,7 @@
 	"sync"
 	"sync/atomic"
 
+	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/trace"
@@ -77,7 +78,9 @@
 	}
 	s.views = nil
 	s.viewMap = nil
-	s.cache.debug.DropSession(DebugSession{s})
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.DropSession(DebugSession{s})
+	}
 }
 
 func (s *Session) Cache() source.Cache {
@@ -142,7 +145,9 @@
 	// Initialize the view without blocking.
 	go v.initialize(xcontext.Detach(ctx), v.snapshot)
 
-	v.session.cache.debug.AddView(debugView{v})
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.AddView(debugView{v})
+	}
 	return v, v.snapshot, nil
 }
 
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index be175a1..0b9cf50 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -23,6 +23,7 @@
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/imports"
+	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
 	"golang.org/x/tools/internal/memoize"
@@ -472,7 +473,7 @@
 	v.session.removeView(ctx, v)
 }
 
-func (v *view) shutdown(context.Context) {
+func (v *view) shutdown(ctx context.Context) {
 	// TODO: Cancel the view's initialization.
 	v.mu.Lock()
 	defer v.mu.Unlock()
@@ -484,7 +485,9 @@
 		os.Remove(v.tempMod.Filename())
 		os.Remove(tempSumFile(v.tempMod.Filename()))
 	}
-	v.session.cache.debug.DropView(debugView{v})
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.DropView(debugView{v})
+	}
 }
 
 // Ignore checks if the given URI is a URI we ignore.
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
index e966166..02f7fab 100644
--- a/internal/lsp/cmd/capabilities_test.go
+++ b/internal/lsp/cmd/capabilities_test.go
@@ -39,7 +39,7 @@
 	params.Capabilities.Workspace.Configuration = true
 
 	// Send an initialize request to the server.
-	c.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), c.Client)
+	c.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), c.Client)
 	result, err := c.Server.Initialize(ctx, params)
 	if err != nil {
 		t.Fatal(err)
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index aeb8bd0..10f2936 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -68,8 +68,6 @@
 	// PrepareOptions is called to update the options when a new view is built.
 	// It is primarily to allow the behavior of gopls to be modified by hooks.
 	PrepareOptions func(*source.Options)
-
-	debug *debug.Instance
 }
 
 // New returns a new Application ready to run.
@@ -131,7 +129,7 @@
 // If no arguments are passed it will invoke the server sub command, as a
 // temporary measure for compatibility.
 func (app *Application) Run(ctx context.Context, args ...string) error {
-	app.debug = debug.NewInstance(app.wd, app.OCAgent)
+	ctx = debug.WithInstance(ctx, app.wd, app.OCAgent)
 	app.Serve.app = app
 	if len(args) == 0 {
 		return tool.Run(ctx, &app.Serve, args)
@@ -191,7 +189,7 @@
 	switch {
 	case app.Remote == "":
 		connection := newConnection(app)
-		connection.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), connection.Client)
+		connection.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), connection.Client)
 		ctx = protocol.WithClient(ctx, connection.Client)
 		return connection, connection.initialize(ctx, app.options)
 	case strings.HasPrefix(app.Remote, "internal@"):
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index bc56a3a..be985ef 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -47,9 +47,9 @@
 }
 
 func testServer(ctx context.Context) *servertest.TCPServer {
-	di := debug.NewInstance("", "")
-	cache := cache.New(nil, di.State)
-	ss := lsprpc.NewStreamServer(cache, false, di)
+	ctx = debug.WithInstance(ctx, "", "")
+	cache := cache.New(ctx, nil)
+	ss := lsprpc.NewStreamServer(cache, false)
 	return servertest.NewTCPServer(ctx, ss)
 }
 
diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go
index 17ee9e3..fa33def 100644
--- a/internal/lsp/cmd/serve.go
+++ b/internal/lsp/cmd/serve.go
@@ -14,6 +14,7 @@
 
 	"golang.org/x/tools/internal/jsonrpc2"
 	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/lsprpc"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/tool"
@@ -55,22 +56,24 @@
 		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
 	}
 
-	closeLog, err := s.app.debug.SetLogFile(s.Logfile)
-	if err != nil {
-		return err
+	di := debug.GetInstance(ctx)
+	if di != nil {
+		closeLog, err := di.SetLogFile(s.Logfile)
+		if err != nil {
+			return err
+		}
+		defer closeLog()
+		di.ServerAddress = s.Address
+		di.DebugAddress = s.Debug
+		di.Serve(ctx)
+		di.MonitorMemory(ctx)
 	}
-	defer closeLog()
-	s.app.debug.ServerAddress = s.Address
-	s.app.debug.DebugAddress = s.Debug
-	s.app.debug.Serve(ctx)
-	s.app.debug.MonitorMemory(ctx)
-
 	var ss jsonrpc2.StreamServer
 	if s.app.Remote != "" {
 		network, addr := parseAddr(s.app.Remote)
-		ss = lsprpc.NewForwarder(network, addr, true, s.app.debug)
+		ss = lsprpc.NewForwarder(network, addr, true)
 	} else {
-		ss = lsprpc.NewStreamServer(cache.New(s.app.options, s.app.debug.State), true, s.app.debug)
+		ss = lsprpc.NewStreamServer(cache.New(ctx, s.app.options), true)
 	}
 
 	if s.Address != "" {
@@ -82,8 +85,8 @@
 		return jsonrpc2.ListenAndServe(ctx, "tcp", addr, ss, s.IdleTimeout)
 	}
 	stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
-	if s.Trace {
-		stream = protocol.LoggingStream(stream, s.app.debug.LogWriter)
+	if s.Trace && di != nil {
+		stream = protocol.LoggingStream(stream, di.LogWriter)
 	}
 	return ss.ServeStream(ctx, stream)
 }
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index 28ed7b9..a9d955d 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -37,6 +37,14 @@
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
+type exporter struct {
+	stderr io.Writer
+}
+
+type instanceKeyType int
+
+const instanceKey = instanceKeyType(0)
+
 // An Instance holds all debug information associated with a gopls instance.
 type Instance struct {
 	Logfile              string
@@ -378,8 +386,26 @@
 	return m
 }
 
-// NewInstance creates debug instance ready for use using the supplied configuration.
-func NewInstance(workdir, agent string) *Instance {
+func init() {
+	export.SetExporter(&exporter{
+		stderr: os.Stderr,
+	})
+}
+
+func GetInstance(ctx context.Context) *Instance {
+	if ctx == nil {
+		return nil
+	}
+	v := ctx.Value(instanceKey)
+	if v == nil {
+		return nil
+	}
+	return v.(*Instance)
+}
+
+// WithInstance creates debug instance ready for use using the supplied
+// configuration and stores it in the returned context.
+func WithInstance(ctx context.Context, workdir, agent string) context.Context {
 	i := &Instance{
 		StartTime:     time.Now(),
 		Workdir:       workdir,
@@ -394,8 +420,7 @@
 	i.rpcs = &rpcs{}
 	i.traces = &traces{}
 	i.State = &State{}
-	export.SetExporter(i)
-	return i
+	return context.WithValue(ctx, instanceKey, i)
 }
 
 // SetLogFile sets the logfile for use with this instance.
@@ -519,7 +544,11 @@
 	return nil
 }
 
-func (i *Instance) StartSpan(ctx context.Context, spn *telemetry.Span) {
+func (e *exporter) StartSpan(ctx context.Context, spn *telemetry.Span) {
+	i := GetInstance(ctx)
+	if i == nil {
+		return
+	}
 	if i.ocagent != nil {
 		i.ocagent.StartSpan(ctx, spn)
 	}
@@ -528,7 +557,11 @@
 	}
 }
 
-func (i *Instance) FinishSpan(ctx context.Context, spn *telemetry.Span) {
+func (e *exporter) FinishSpan(ctx context.Context, spn *telemetry.Span) {
+	i := GetInstance(ctx)
+	if i == nil {
+		return
+	}
 	if i.ocagent != nil {
 		i.ocagent.FinishSpan(ctx, spn)
 	}
@@ -537,14 +570,13 @@
 	}
 }
 
-//TODO: remove this hack
-// capture stderr at startup because it gets modified in a way that this
-// logger should not respect
-var stderr = os.Stderr
-
-func (i *Instance) Log(ctx context.Context, event telemetry.Event) {
-	if event.Error != nil {
-		fmt.Fprintf(stderr, "%v\n", event)
+func (e *exporter) Log(ctx context.Context, event telemetry.Event) {
+	i := GetInstance(ctx)
+	if event.Error != nil || i == nil {
+		fmt.Fprintf(e.stderr, "%v\n", event)
+	}
+	if i == nil {
+		return
 	}
 	protocol.LogEvent(ctx, event)
 	if i.ocagent != nil {
@@ -552,7 +584,11 @@
 	}
 }
 
-func (i *Instance) Metric(ctx context.Context, data telemetry.MetricData) {
+func (e *exporter) Metric(ctx context.Context, data telemetry.MetricData) {
+	i := GetInstance(ctx)
+	if i == nil {
+		return
+	}
 	if i.ocagent != nil {
 		i.ocagent.Metric(ctx, data)
 	}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 2c3ae63..dee2c6d 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -49,8 +49,8 @@
 	for _, datum := range data {
 		defer datum.Exported.Cleanup()
 
-		cache := cache.New(nil, nil)
-		session := cache.NewSession()
+		cache := cache.New(ctx, nil)
+		session := cache.NewSession(ctx)
 		options := tests.DefaultOptions()
 		session.SetOptions(options)
 		options.Env = datum.Config.Env
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index 6d7e983..3bbc42a 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -35,7 +35,6 @@
 // streams as a new LSP session, using a shared cache.
 type StreamServer struct {
 	withTelemetry bool
-	debug         *debug.Instance
 	cache         *cache.Cache
 
 	// serverForTest may be set to a test fake for testing.
@@ -47,10 +46,9 @@
 // NewStreamServer creates a StreamServer using the shared cache. If
 // withTelemetry is true, each session is instrumented with telemetry that
 // records RPC statistics.
-func NewStreamServer(cache *cache.Cache, withTelemetry bool, debugInstance *debug.Instance) *StreamServer {
+func NewStreamServer(cache *cache.Cache, withTelemetry bool) *StreamServer {
 	s := &StreamServer{
 		withTelemetry: withTelemetry,
-		debug:         debugInstance,
 		cache:         cache,
 	}
 	return s
@@ -118,16 +116,17 @@
 
 	conn := jsonrpc2.NewConn(stream)
 	client := protocol.ClientDispatcher(conn)
-	session := s.cache.NewSession()
+	session := s.cache.NewSession(ctx)
 	dc := &debugClient{
 		debugInstance: debugInstance{
 			id: strconv.FormatInt(index, 10),
 		},
 		session: session,
 	}
-	s.debug.State.AddClient(dc)
-	defer s.debug.State.DropClient(dc)
-
+	if di := debug.GetInstance(ctx); di != nil {
+		di.State.AddClient(dc)
+		defer di.State.DropClient(dc)
+	}
 	server := s.serverForTest
 	if server == nil {
 		server = lsp.NewServer(session, client)
@@ -148,7 +147,6 @@
 	}
 	conn.AddHandler(&handshaker{
 		client:    dc,
-		debug:     s.debug,
 		goplsPath: executable,
 	})
 	return conn.Run(protocol.WithClient(ctx, client))
@@ -168,13 +166,12 @@
 	withTelemetry bool
 	dialTimeout   time.Duration
 	retries       int
-	debug         *debug.Instance
 	goplsPath     string
 }
 
 // NewForwarder creates a new Forwarder, ready to forward connections to the
 // remote server specified by network and addr.
-func NewForwarder(network, addr string, withTelemetry bool, debugInstance *debug.Instance) *Forwarder {
+func NewForwarder(network, addr string, withTelemetry bool) *Forwarder {
 	gp, err := os.Executable()
 	if err != nil {
 		stdlog.Printf("error getting gopls path for forwarder: %v", err)
@@ -187,7 +184,6 @@
 		withTelemetry: withTelemetry,
 		dialTimeout:   1 * time.Second,
 		retries:       5,
-		debug:         debugInstance,
 		goplsPath:     gp,
 	}
 }
@@ -224,30 +220,35 @@
 	// Do a handshake with the server instance to exchange debug information.
 	index := atomic.AddInt64(&serverIndex, 1)
 	serverID := strconv.FormatInt(index, 10)
+	di := debug.GetInstance(ctx)
 	var (
 		hreq = handshakeRequest{
 			ServerID:  serverID,
-			Logfile:   f.debug.Logfile,
-			DebugAddr: f.debug.ListenedDebugAddress,
 			GoplsPath: f.goplsPath,
 		}
 		hresp handshakeResponse
 	)
+	if di != nil {
+		hreq.Logfile = di.Logfile
+		hreq.DebugAddr = di.ListenedDebugAddress
+	}
 	if err := serverConn.Call(ctx, handshakeMethod, hreq, &hresp); err != nil {
 		log.Error(ctx, "forwarder: gopls handshake failed", err)
 	}
 	if hresp.GoplsPath != f.goplsPath {
 		log.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
 	}
-	f.debug.State.AddServer(debugServer{
-		debugInstance: debugInstance{
-			id:           serverID,
-			logfile:      hresp.Logfile,
-			debugAddress: hresp.DebugAddr,
-			goplsPath:    hresp.GoplsPath,
-		},
-		clientID: hresp.ClientID,
-	})
+	if di != nil {
+		di.State.AddServer(debugServer{
+			debugInstance: debugInstance{
+				id:           serverID,
+				logfile:      hresp.Logfile,
+				debugAddress: hresp.DebugAddr,
+				goplsPath:    hresp.GoplsPath,
+			},
+			clientID: hresp.ClientID,
+		})
+	}
 	g.Go(func() error {
 		return clientConn.Run(ctx)
 	})
@@ -375,7 +376,6 @@
 type handshaker struct {
 	jsonrpc2.EmptyHandler
 	client    *debugClient
-	debug     *debug.Instance
 	goplsPath string
 }
 
@@ -410,10 +410,13 @@
 		resp := handshakeResponse{
 			ClientID:  h.client.id,
 			SessionID: cache.DebugSession{Session: h.client.session}.ID(),
-			Logfile:   h.debug.Logfile,
-			DebugAddr: h.debug.ListenedDebugAddress,
 			GoplsPath: h.goplsPath,
 		}
+		if di := debug.GetInstance(ctx); di != nil {
+			resp.Logfile = di.Logfile
+			resp.DebugAddr = di.ListenedDebugAddress
+		}
+
 		if err := r.Reply(ctx, resp, nil); err != nil {
 			log.Error(ctx, "replying to handshake", err)
 		}
diff --git a/internal/lsp/lsprpc/lsprpc_test.go b/internal/lsp/lsprpc/lsprpc_test.go
index 1bca640..de5ea5c 100644
--- a/internal/lsp/lsprpc/lsprpc_test.go
+++ b/internal/lsp/lsprpc/lsprpc_test.go
@@ -48,8 +48,8 @@
 	server := pingServer{}
 	client := fakeClient{logs: make(chan string, 10)}
 
-	di := debug.NewInstance("", "")
-	ss := NewStreamServer(cache.New(nil, di.State), false, di)
+	ctx = debug.WithInstance(ctx, "", "")
+	ss := NewStreamServer(cache.New(ctx, nil), false)
 	ss.serverForTest = server
 	ts := servertest.NewPipeServer(ctx, ss)
 	defer ts.Close()
@@ -104,15 +104,16 @@
 	server := waitableServer{
 		started: make(chan struct{}),
 	}
-	diserve := debug.NewInstance("", "")
-	ss := NewStreamServer(cache.New(nil, diserve.State), false, diserve)
+	baseCtx := context.Background()
+	serveCtx := debug.WithInstance(baseCtx, "", "")
+	ss := NewStreamServer(cache.New(serveCtx, nil), false)
 	ss.serverForTest = server
-	ctx := context.Background()
-	tsDirect := servertest.NewTCPServer(ctx, ss)
+	tsDirect := servertest.NewTCPServer(serveCtx, ss)
 	defer tsDirect.Close()
 
-	forwarder := NewForwarder("tcp", tsDirect.Addr, false, debug.NewInstance("", ""))
-	tsForwarded := servertest.NewPipeServer(ctx, forwarder)
+	forwarderCtx := debug.WithInstance(baseCtx, "", "")
+	forwarder := NewForwarder("tcp", tsDirect.Addr, false)
+	tsForwarded := servertest.NewPipeServer(forwarderCtx, forwarder)
 	defer tsForwarded.Close()
 
 	tests := []struct {
@@ -125,7 +126,7 @@
 
 	for _, test := range tests {
 		t.Run(test.serverType, func(t *testing.T) {
-			cc := test.ts.Connect(ctx)
+			cc := test.ts.Connect(baseCtx)
 			cc.AddHandler(protocol.Canceller{})
 			ctx := context.Background()
 			ctx1, cancel1 := context.WithCancel(ctx)
@@ -177,16 +178,16 @@
 	resetExitFuncs := OverrideExitFuncsForTest()
 	defer resetExitFuncs()
 
-	clientDebug := debug.NewInstance("", "")
-	serverDebug := debug.NewInstance("", "")
+	baseCtx := context.Background()
+	clientCtx := debug.WithInstance(baseCtx, "", "")
+	serverCtx := debug.WithInstance(baseCtx, "", "")
 
-	cache := cache.New(nil, serverDebug.State)
-	ss := NewStreamServer(cache, false, serverDebug)
-	ctx := context.Background()
-	tsBackend := servertest.NewTCPServer(ctx, ss)
+	cache := cache.New(serverCtx, nil)
+	ss := NewStreamServer(cache, false)
+	tsBackend := servertest.NewTCPServer(serverCtx, ss)
 
-	forwarder := NewForwarder("tcp", tsBackend.Addr, false, clientDebug)
-	tsForwarder := servertest.NewPipeServer(ctx, forwarder)
+	forwarder := NewForwarder("tcp", tsBackend.Addr, false)
+	tsForwarder := servertest.NewPipeServer(clientCtx, forwarder)
 
 	ws, err := fake.NewWorkspace("gopls-lsprpc-test", []byte(exampleProgram))
 	if err != nil {
@@ -194,31 +195,33 @@
 	}
 	defer ws.Close()
 
-	conn1 := tsForwarder.Connect(ctx)
-	ed1, err := fake.NewConnectedEditor(ctx, ws, conn1)
+	conn1 := tsForwarder.Connect(clientCtx)
+	ed1, err := fake.NewConnectedEditor(clientCtx, ws, conn1)
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer ed1.Shutdown(ctx)
-	conn2 := tsBackend.Connect(ctx)
-	ed2, err := fake.NewConnectedEditor(ctx, ws, conn2)
+	defer ed1.Shutdown(clientCtx)
+	conn2 := tsBackend.Connect(baseCtx)
+	ed2, err := fake.NewConnectedEditor(baseCtx, ws, conn2)
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer ed2.Shutdown(ctx)
+	defer ed2.Shutdown(baseCtx)
 
+	serverDebug := debug.GetInstance(serverCtx)
 	if got, want := len(serverDebug.State.Clients()), 2; got != want {
 		t.Errorf("len(server:Clients) = %d, want %d", got, want)
 	}
 	if got, want := len(serverDebug.State.Sessions()), 2; got != want {
 		t.Errorf("len(server:Sessions) = %d, want %d", got, want)
 	}
+	clientDebug := debug.GetInstance(clientCtx)
 	if got, want := len(clientDebug.State.Servers()), 1; got != want {
 		t.Errorf("len(client:Servers) = %d, want %d", got, want)
 	}
 	// Close one of the connections to verify that the client and session were
 	// dropped.
-	if err := ed1.Shutdown(ctx); err != nil {
+	if err := ed1.Shutdown(clientCtx); err != nil {
 		t.Fatal(err)
 	}
 	if got, want := len(serverDebug.State.Sessions()), 1; got != want {
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index ada211c..f4392cd 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -24,7 +24,7 @@
 func TestModfileRemainsUnchanged(t *testing.T) {
 	ctx := tests.Context(t)
 	cache := cache.New(nil, nil)
-	session := cache.NewSession()
+	session := cache.NewSession(ctx)
 	options := tests.DefaultOptions()
 	options.TempModfile = true
 	options.Env = append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT=")
diff --git a/internal/lsp/regtest/env.go b/internal/lsp/regtest/env.go
index e4d0bb2..91089b3 100644
--- a/internal/lsp/regtest/env.go
+++ b/internal/lsp/regtest/env.go
@@ -80,8 +80,9 @@
 	r.mu.Lock()
 	defer r.mu.Unlock()
 	if r.ts == nil {
-		di := debug.NewInstance("", "")
-		ss := lsprpc.NewStreamServer(cache.New(nil, di.State), false, di)
+		ctx := context.Background()
+		ctx = debug.WithInstance(ctx, "", "")
+		ss := lsprpc.NewStreamServer(cache.New(ctx, nil), false)
 		r.ts = servertest.NewTCPServer(context.Background(), ss)
 	}
 	return r.ts
@@ -186,8 +187,8 @@
 }
 
 func (r *Runner) singletonEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
-	di := debug.NewInstance("", "")
-	ss := lsprpc.NewStreamServer(cache.New(nil, di.State), false, di)
+	ctx = debug.WithInstance(ctx, "", "")
+	ss := lsprpc.NewStreamServer(cache.New(ctx, nil), false)
 	ts := servertest.NewPipeServer(ctx, ss)
 	cleanup := func() {
 		ts.Close()
@@ -200,8 +201,9 @@
 }
 
 func (r *Runner) forwardedEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	ctx = debug.WithInstance(ctx, "", "")
 	ts := r.getTestServer()
-	forwarder := lsprpc.NewForwarder("tcp", ts.Addr, false, debug.NewInstance("", ""))
+	forwarder := lsprpc.NewForwarder("tcp", ts.Addr, false)
 	ts2 := servertest.NewPipeServer(ctx, forwarder)
 	cleanup := func() {
 		ts2.Close()
@@ -210,10 +212,11 @@
 }
 
 func (r *Runner) separateProcessEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	ctx = debug.WithInstance(ctx, "", "")
 	socket := r.getRemoteSocket(t)
 	// TODO(rfindley): can we use the autostart behavior here, instead of
 	// pre-starting the remote?
-	forwarder := lsprpc.NewForwarder("unix", socket, false, debug.NewInstance("", ""))
+	forwarder := lsprpc.NewForwarder("unix", socket, false)
 	ts2 := servertest.NewPipeServer(ctx, forwarder)
 	cleanup := func() {
 		ts2.Close()
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index b3ae9f4..06144af 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -47,8 +47,8 @@
 	for _, datum := range data {
 		defer datum.Exported.Cleanup()
 
-		cache := cache.New(nil, nil)
-		session := cache.NewSession()
+		cache := cache.New(ctx, nil)
+		session := cache.NewSession(ctx)
 		options := tests.DefaultOptions()
 		options.Env = datum.Config.Env
 		view, _, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)