this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

lsp/server: protect lsp state modifications with mutex

So far the LSP server has a strict 1-to-1 relationship with a connection
to an editor. Consequently no locking has been needed because all
requests come from the same source, and due to the way the JSON RPC
connection is configured, there's been no possibility of concurrent
requests being sent to any server.

But this is about to change and so a single server will have to deal
with having to do work for more than one client. To make this safe, we
add a mutex, and make sure it's held for every implemented LSP method.

Signed-off-by: Matthew Sackman <matthew@cue.works>
Change-Id: I75a8ffc3a50bcd120d4edfeea03b29a0c56bdea0
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1232174
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+70 -8
+2 -8
internal/golangorgx/gopls/lsprpc/lsprpc.go
··· 41 41 42 42 // optionsOverrides is passed to newly created workspaces. 43 43 optionsOverrides func(*settings.Options) 44 - 45 - // serverForTest may be set to a test fake for testing. 46 - serverForTest server.ServerWithID 47 44 } 48 45 49 46 // NewStreamServer creates a StreamServer using the shared cache. ··· 55 52 // incoming streams using a new lsp server. 56 53 func (s *streamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error { 57 54 client := protocol.ClientDispatcher(conn) 58 - svr := s.serverForTest 59 - if svr == nil { 60 - options := settings.DefaultOptions(s.optionsOverrides) 61 - svr = server.New(s.cache, client, options) 62 - } 55 + options := settings.DefaultOptions(s.optionsOverrides) 56 + svr := server.New(s.cache, client, options) 63 57 svrID := svr.ID() 64 58 // Clients may or may not send a shutdown message. Make sure the server is 65 59 // shut down.
+3
internal/lsp/server/codeaction.go
··· 33 33 } 34 34 35 35 func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { 36 + s.lock.Lock() 37 + defer s.lock.Unlock() 38 + 36 39 var codeActions []protocol.CodeAction 37 40 38 41 convertToStructEdit, err := s.workspace.CodeActionConvertToStruct(ctx, params)
+18
internal/lsp/server/eval.go
··· 22 22 ) 23 23 24 24 func (s *server) Definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) { 25 + s.lock.Lock() 26 + defer s.lock.Unlock() 27 + 25 28 uri := params.TextDocument.URI 26 29 w := s.workspace 27 30 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadNothing) ··· 32 35 } 33 36 34 37 func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { 38 + s.lock.Lock() 39 + defer s.lock.Unlock() 40 + 35 41 uri := params.TextDocument.URI 36 42 w := s.workspace 37 43 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadAllIfNonCue) ··· 42 48 } 43 49 44 50 func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 51 + s.lock.Lock() 52 + defer s.lock.Unlock() 53 + 45 54 uri := params.TextDocument.URI 46 55 w := s.workspace 47 56 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadAllIfNonCue) ··· 52 61 } 53 62 54 63 func (s *server) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) { 64 + s.lock.Lock() 65 + defer s.lock.Unlock() 66 + 55 67 uri := params.TextDocument.URI 56 68 w := s.workspace 57 69 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadAll) ··· 62 74 } 63 75 64 76 func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { 77 + s.lock.Lock() 78 + defer s.lock.Unlock() 79 + 65 80 uri := params.TextDocument.URI 66 81 w := s.workspace 67 82 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadAll) ··· 72 87 } 73 88 74 89 func (s *server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.PrepareRenamePlaceholder, error) { 90 + s.lock.Lock() 91 + defer s.lock.Unlock() 92 + 75 93 uri := params.TextDocument.URI 76 94 w := s.workspace 77 95 file, fe, srcMapper, err := w.FileEvaluatorForURI(uri, cache.LoadAll)
+3
internal/lsp/server/file.go
··· 21 21 ) 22 22 23 23 func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]any, error) { 24 + s.lock.Lock() 25 + defer s.lock.Unlock() 26 + 24 27 root := s.workspace.DocumentSymbols(params.TextDocument.URI) 25 28 if len(root) == 0 { 26 29 return nil, nil
+3
internal/lsp/server/format.go
··· 25 25 // 26 26 // Formatting implements [protocol.Server] 27 27 func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { 28 + s.lock.Lock() 29 + defer s.lock.Unlock() 30 + 28 31 _, done := event.Start(ctx, "lsp.Server.formatting", tag.URI.Of(params.TextDocument.URI)) 29 32 defer done() 30 33
+6
internal/lsp/server/initialize.go
··· 50 50 // 51 51 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize 52 52 func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { 53 + s.lock.Lock() 54 + defer s.lock.Unlock() 55 + 53 56 ctx, done := event.Start(ctx, "lsp.Server.initialize") 54 57 defer done() 55 58 ··· 147 150 // 148 151 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialized 149 152 func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 153 + s.lock.Lock() 154 + defer s.lock.Unlock() 155 + 150 156 ctx, done := event.Start(ctx, "lsp.Server.initialized") 151 157 defer done() 152 158
+14
internal/lsp/server/server.go
··· 11 11 "fmt" 12 12 "os" 13 13 "strconv" 14 + "sync" 14 15 "sync/atomic" 15 16 16 17 "cuelang.org/go/internal/golangorgx/gopls/progress" ··· 79 80 type server struct { 80 81 id string 81 82 83 + // lock must be held for every public API method, and protects all 84 + // state within the server. 85 + lock sync.Mutex 86 + 82 87 client protocol.ClientCloser 83 88 cache *cache.Cache 84 89 workspace *cache.Workspace ··· 113 118 // sent, instead it should wait for an exit message, which is 114 119 // asynchronous. 115 120 func (s *server) Shutdown(ctx context.Context) error { 121 + s.lock.Lock() 122 + defer s.lock.Unlock() 123 + 116 124 ctx, done := event.Start(ctx, "lsp.Server.shutdown") 117 125 defer done() 118 126 ··· 139 147 // 140 148 // This is asynchronous - it does not get a response. 141 149 func (s *server) Exit(ctx context.Context) error { 150 + s.lock.Lock() 151 + defer s.lock.Unlock() 152 + 142 153 _, done := event.Start(ctx, "lsp.Server.exit") 143 154 defer done() 144 155 ··· 156 167 // WorkDoneProgressCancel is a message from the editor/client 157 168 // requesting the cancellation of a long-running process. 158 169 func (s *server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { 170 + s.lock.Lock() 171 + defer s.lock.Unlock() 172 + 159 173 ctx, done := event.Start(ctx, "lsp.Server.workDoneProgressCancel") 160 174 defer done() 161 175
+15
internal/lsp/server/text_synchronization.go
··· 83 83 } 84 84 85 85 func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { 86 + s.lock.Lock() 87 + defer s.lock.Unlock() 88 + 86 89 ctx, done := event.Start(ctx, "lsp.Server.didOpen", tag.URI.Of(params.TextDocument.URI)) 87 90 defer done() 88 91 ··· 104 107 } 105 108 106 109 func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { 110 + s.lock.Lock() 111 + defer s.lock.Unlock() 112 + 107 113 ctx, done := event.Start(ctx, "lsp.Server.didChange", tag.URI.Of(params.TextDocument.URI)) 108 114 defer done() 109 115 ··· 117 123 } 118 124 119 125 func (s *server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { 126 + s.lock.Lock() 127 + defer s.lock.Unlock() 128 + 120 129 ctx, done := event.Start(ctx, "lsp.Server.didChangeWatchedFiles") 121 130 defer done() 122 131 ··· 132 141 } 133 142 134 143 func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { 144 + s.lock.Lock() 145 + defer s.lock.Unlock() 146 + 135 147 ctx, done := event.Start(ctx, "lsp.Server.didClose", tag.URI.Of(params.TextDocument.URI)) 136 148 defer done() 137 149 ··· 143 155 } 144 156 145 157 func (s *server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { 158 + s.lock.Lock() 159 + defer s.lock.Unlock() 160 + 146 161 ctx, done := event.Start(ctx, "lsp.Server.didSave", tag.URI.Of(params.TextDocument.URI)) 147 162 defer done() 148 163
+6
internal/lsp/server/workspace.go
··· 54 54 } 55 55 56 56 func (s *server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { 57 + s.lock.Lock() 58 + defer s.lock.Unlock() 59 + 57 60 for _, folder := range params.Event.Removed { 58 61 dir, err := protocol.ParseDocumentURI(folder.URI) 59 62 if err != nil { ··· 74 77 } 75 78 76 79 func (s *server) DidChangeConfiguration(ctx context.Context, _ *protocol.DidChangeConfigurationParams) error { 80 + s.lock.Lock() 81 + defer s.lock.Unlock() 82 + 77 83 ctx, done := event.Start(ctx, "lsp.Server.didChangeConfiguration") 78 84 defer done() 79 85