fix: faster shutdown (#1570)

Carlos Alexandro Becker created

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

internal/agent/tools/diagnostics.go |  2 
internal/agent/tools/mcp/init.go    | 31 ++++++++++++++++++++---------
internal/app/app.go                 | 32 +++++++++++++++++++++---------
internal/app/lsp.go                 |  8 +++---
4 files changed, 48 insertions(+), 25 deletions(-)

Detailed changes

internal/agent/tools/diagnostics.go 🔗

@@ -94,7 +94,7 @@ func getDiagnostics(filePath string, lsps *csync.Map[string, *lsp.Client]) strin
 	}
 
 	out := output.String()
-	slog.Info("Diagnostics", "output", out)
+	slog.Debug("Diagnostics", "output", out)
 	return out
 }
 

internal/agent/tools/mcp/init.go 🔗

@@ -109,14 +109,26 @@ func GetState(name string) (ClientInfo, bool) {
 // Close closes all MCP clients. This should be called during application shutdown.
 func Close() error {
 	var errs []error
+	var wg sync.WaitGroup
 	for name, session := range sessions.Seq2() {
-		if err := session.Close(); err != nil &&
-			!errors.Is(err, io.EOF) &&
-			!errors.Is(err, context.Canceled) &&
-			err.Error() != "signal: killed" {
-			errs = append(errs, fmt.Errorf("close mcp: %s: %w", name, err))
-		}
+		wg.Go(func() {
+			done := make(chan bool, 1)
+			go func() {
+				if err := session.Close(); err != nil &&
+					!errors.Is(err, io.EOF) &&
+					!errors.Is(err, context.Canceled) &&
+					err.Error() != "signal: killed" {
+					errs = append(errs, fmt.Errorf("close mcp: %s: %w", name, err))
+				}
+				done <- true
+			}()
+			select {
+			case <-done:
+			case <-time.After(time.Millisecond * 250):
+			}
+		})
 	}
+	wg.Wait()
 	broker.Shutdown()
 	return errors.Join(errs...)
 }
@@ -279,9 +291,8 @@ func createSession(ctx context.Context, name string, m config.MCPConfig, resolve
 				})
 			},
 			LoggingMessageHandler: func(_ context.Context, req *mcp.LoggingMessageRequest) {
-				slog.Info("mcp log", "name", name, "data", req.Params.Data)
+				slog.Info("MCP log", "name", name, "data", req.Params.Data)
 			},
-			KeepAlive: time.Minute * 10,
 		},
 	)
 
@@ -289,14 +300,14 @@ func createSession(ctx context.Context, name string, m config.MCPConfig, resolve
 	if err != nil {
 		err = maybeStdioErr(err, transport)
 		updateState(name, StateError, maybeTimeoutErr(err, timeout), nil, Counts{})
-		slog.Error("error starting mcp client", "error", err, "name", name)
+		slog.Error("MCP client failed to initialize", "error", err, "name", name)
 		cancel()
 		cancelTimer.Stop()
 		return nil, err
 	}
 
 	cancelTimer.Stop()
-	slog.Info("Initialized mcp client", "name", name)
+	slog.Info("MCP client initialized", "name", name)
 	return session, nil
 }
 

internal/app/app.go 🔗

@@ -370,30 +370,42 @@ func (app *App) Subscribe(program *tea.Program) {
 
 // Shutdown performs a graceful shutdown of the application.
 func (app *App) Shutdown() {
+	start := time.Now()
+	defer func() { slog.Info("Shutdown took " + time.Since(start).String()) }()
+	var wg sync.WaitGroup
 	if app.AgentCoordinator != nil {
-		app.AgentCoordinator.CancelAll()
+		wg.Go(func() {
+			app.AgentCoordinator.CancelAll()
+		})
 	}
 
 	// Kill all background shells.
-	shell.GetBackgroundShellManager().KillAll()
+	wg.Go(func() {
+		shell.GetBackgroundShellManager().KillAll()
+	})
 
 	// Shutdown all LSP clients.
 	for name, client := range app.LSPClients.Seq2() {
-		shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
-		if err := client.Close(shutdownCtx); err != nil {
-			slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
-		}
-		cancel()
+		wg.Go(func() {
+			shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
+			defer cancel()
+			if err := client.Close(shutdownCtx); err != nil {
+				slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
+			}
+		})
 	}
 
 	// Call call cleanup functions.
 	for _, cleanup := range app.cleanupFuncs {
 		if cleanup != nil {
-			if err := cleanup(); err != nil {
-				slog.Error("Failed to cleanup app properly on shutdown", "error", err)
-			}
+			wg.Go(func() {
+				if err := cleanup(); err != nil {
+					slog.Error("Failed to cleanup app properly on shutdown", "error", err)
+				}
+			})
 		}
 	}
+	wg.Wait()
 }
 
 // checkForUpdates checks for available updates.

internal/app/lsp.go 🔗

@@ -23,11 +23,11 @@ func (app *App) initLSPClients(ctx context.Context) {
 
 // createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
 func (app *App) createAndStartLSPClient(ctx context.Context, name string, config config.LSPConfig) {
-	slog.Info("Creating LSP client", "name", name, "command", config.Command, "fileTypes", config.FileTypes, "args", config.Args)
+	slog.Debug("Creating LSP client", "name", name, "command", config.Command, "fileTypes", config.FileTypes, "args", config.Args)
 
 	// Check if any root markers exist in the working directory (config now has defaults)
 	if !lsp.HasRootMarkers(app.config.WorkingDir(), config.RootMarkers) {
-		slog.Info("Skipping LSP client - no root markers found", "name", name, "rootMarkers", config.RootMarkers)
+		slog.Debug("Skipping LSP client: no root markers found", "name", name, "rootMarkers", config.RootMarkers)
 		updateLSPState(name, lsp.StateDisabled, nil, nil, 0)
 		return
 	}
@@ -53,7 +53,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
 	// Initialize LSP client.
 	_, err = lspClient.Initialize(initCtx, app.config.WorkingDir())
 	if err != nil {
-		slog.Error("Initialize failed", "name", name, "error", err)
+		slog.Error("LSP client initialization failed", "name", name, "error", err)
 		updateLSPState(name, lsp.StateError, err, lspClient, 0)
 		lspClient.Close(ctx)
 		return
@@ -68,7 +68,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
 		updateLSPState(name, lsp.StateError, err, lspClient, 0)
 	} else {
 		// Server reached a ready state scuccessfully.
-		slog.Info("LSP server is ready", "name", name)
+		slog.Debug("LSP server is ready", "name", name)
 		lspClient.SetServerState(lsp.StateReady)
 		updateLSPState(name, lsp.StateReady, nil, lspClient, 0)
 	}