internal/lsp: handle undelivered diagnostics

This change adds a cache of undelivered diagnostics on the server-side.
If we fail to send a diagnostic once, we will retry the next time that
the server sends diagnostics.

Change-Id: I161dfad8ea1d2cfdcee933baed2d6872dc03b0c0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/167737
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-03-14 17:19:01 -04:00
parent e2f00d1e07
commit cf22ef0385
3 changed files with 39 additions and 13 deletions

View File

@ -14,7 +14,7 @@ import (
) )
func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content string) error { func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content string) error {
if err := s.setContent(ctx, uri, []byte(content)); err != nil { if err := s.view.SetContent(ctx, uri, []byte(content)); err != nil {
return err return err
} }
go func() { go func() {
@ -26,22 +26,40 @@ func (s *Server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str
if err != nil { if err != nil {
return // handle error? return // handle error?
} }
s.undeliveredMu.Lock()
defer s.undeliveredMu.Unlock()
for uri, diagnostics := range reports { for uri, diagnostics := range reports {
protocolDiagnostics, err := toProtocolDiagnostics(ctx, s.view, diagnostics) if err := s.publishDiagnostics(ctx, uri, diagnostics); err != nil {
if err != nil { s.undelivered[uri] = diagnostics
continue // handle errors? continue
} }
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ // In case we had old, undelivered diagnostics.
Diagnostics: protocolDiagnostics, delete(s.undelivered, uri)
URI: protocol.NewURI(uri), }
}) // Anytime we compute diagnostics, make sure to also send along any
// undelivered ones (only for remaining URIs).
for uri, diagnostics := range s.undelivered {
s.publishDiagnostics(ctx, uri, diagnostics)
// If we fail to deliver the same diagnostics twice, just give up.
delete(s.undelivered, uri)
} }
}() }()
return nil return nil
} }
func (s *Server) setContent(ctx context.Context, uri span.URI, content []byte) error { func (s *Server) publishDiagnostics(ctx context.Context, uri span.URI, diagnostics []source.Diagnostic) error {
return s.view.SetContent(ctx, uri, content) protocolDiagnostics, err := toProtocolDiagnostics(ctx, s.view, diagnostics)
if err != nil {
return err
}
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
Diagnostics: protocolDiagnostics,
URI: protocol.NewURI(uri),
})
return nil
} }
func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) ([]protocol.Diagnostic, error) { func toProtocolDiagnostics(ctx context.Context, v source.View, diagnostics []source.Diagnostic) ([]protocol.Diagnostic, error) {

View File

@ -80,7 +80,13 @@ type Server struct {
textDocumentSyncKind protocol.TextDocumentSyncKind textDocumentSyncKind protocol.TextDocumentSyncKind
view *cache.View viewMu sync.Mutex
view *cache.View
// undelivered is a cache of any diagnostics that the server
// failed to deliver for some reason.
undeliveredMu sync.Mutex
undelivered map[span.URI][]source.Diagnostic
} }
func (s *Server) Run(ctx context.Context) error { func (s *Server) Run(ctx context.Context) error {
@ -302,7 +308,9 @@ func (s *Server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) e
} }
func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
s.setContent(ctx, span.NewURI(params.TextDocument.URI), nil) if err := s.view.SetContent(ctx, span.NewURI(params.TextDocument.URI), nil); err != nil {
return err
}
return nil return nil
} }

View File

@ -21,9 +21,9 @@ import (
// package does not directly access the file system. // package does not directly access the file system.
type View interface { type View interface {
Logger() xlog.Logger Logger() xlog.Logger
FileSet() *token.FileSet
GetFile(ctx context.Context, uri span.URI) (File, error) GetFile(ctx context.Context, uri span.URI) (File, error)
SetContent(ctx context.Context, uri span.URI, content []byte) error SetContent(ctx context.Context, uri span.URI, content []byte) error
FileSet() *token.FileSet
} }
// File represents a Go source file that has been type-checked. It is the input // File represents a Go source file that has been type-checked. It is the input