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>
This commit is contained in:
Ian Cottrell 2019-07-10 21:11:23 -04:00
parent 2868181328
commit 5f9351755f
16 changed files with 85 additions and 92 deletions

View File

@ -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,

View File

@ -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{}{}

View File

@ -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

View File

@ -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)
}

View File

@ -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" {

View File

@ -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

View File

@ -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),
},

View File

@ -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) }

View File

@ -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})

View File

@ -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 {

View File

@ -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

View File

@ -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")
}

View File

@ -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
}

View File

@ -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) }

View File

@ -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()

View File

@ -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) }