fix(lsp): prevent nil client from being stored in clients map (#2262)

Austin Cherry created

Change summary

internal/lsp/manager.go | 42 ++++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 14 deletions(-)

Detailed changes

internal/lsp/manager.go 🔗

@@ -178,24 +178,38 @@ func (s *Manager) startServer(ctx context.Context, name, filepath string, server
 		return
 	}
 
-	// check again in case another goroutine started it in the meantime
-	var err error
-	client := s.clients.GetOrSet(name, func() *Client {
-		var cli *Client
-		cli, err = New(
-			ctx,
-			name,
-			cfg,
-			s.cfg.Resolver(),
-			s.cfg.WorkingDir(),
-			s.cfg.Options.DebugLSP,
-		)
-		return cli
-	})
+	// Check again in case another goroutine started it in the meantime.
+	if client, ok := s.clients.Get(name); ok {
+		switch client.GetServerState() {
+		case StateReady, StateStarting, StateDisabled:
+			s.callback(name, client)
+			return
+		}
+	}
+
+	client, err := New(
+		ctx,
+		name,
+		cfg,
+		s.cfg.Resolver(),
+		s.cfg.WorkingDir(),
+		s.cfg.Options.DebugLSP,
+	)
 	if err != nil {
 		slog.Error("Failed to create LSP client", "name", name, "error", err)
 		return
 	}
+	// Only store non-nil clients. If another goroutine raced us,
+	// prefer the already-stored client.
+	if existing, ok := s.clients.Get(name); ok {
+		switch existing.GetServerState() {
+		case StateReady, StateStarting, StateDisabled:
+			client.Close(ctx)
+			s.callback(name, existing)
+			return
+		}
+	}
+	s.clients.Set(name, client)
 	defer func() {
 		s.callback(name, client)
 	}()