Stop stops the CLI server and closes all active sessions. This method performs graceful cleanup: 1. Closes all active sessions (releases in-memory resources) 2. Requests runtime shutdown for SDK-owned CLI processes 3. Closes the JSON-RPC connection 4. Terminates the CLI server process (if spawned b
()
| 428 | // log.Printf("Cleanup error: %v", err) |
| 429 | // } |
| 430 | func (c *Client) Stop() error { |
| 431 | var errs []error |
| 432 | |
| 433 | // Disconnect all active sessions |
| 434 | c.sessionsMux.Lock() |
| 435 | sessions := make([]*Session, 0, len(c.sessions)) |
| 436 | for _, session := range c.sessions { |
| 437 | sessions = append(sessions, session) |
| 438 | } |
| 439 | c.sessionsMux.Unlock() |
| 440 | |
| 441 | for _, session := range sessions { |
| 442 | if err := session.Disconnect(); err != nil { |
| 443 | errs = append(errs, fmt.Errorf("failed to disconnect session %s: %w", session.SessionID, err)) |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | c.sessionsMux.Lock() |
| 448 | c.sessions = make(map[string]*Session) |
| 449 | c.sessionsMux.Unlock() |
| 450 | |
| 451 | c.startStopMux.Lock() |
| 452 | defer c.startStopMux.Unlock() |
| 453 | |
| 454 | if c.process != nil && !c.isExternalServer && c.RPC != nil { |
| 455 | rpcClient := c.RPC |
| 456 | runtimeShutdownStart := time.Now() |
| 457 | shutdownDone := make(chan error, 1) |
| 458 | go func() { |
| 459 | _, err := rpcClient.Runtime.Shutdown(context.Background()) |
| 460 | shutdownDone <- err |
| 461 | }() |
| 462 | |
| 463 | select { |
| 464 | case err := <-shutdownDone: |
| 465 | if err != nil { |
| 466 | c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown failed") |
| 467 | errs = append(errs, fmt.Errorf("failed to gracefully shut down runtime: %w", err)) |
| 468 | } else { |
| 469 | c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown complete") |
| 470 | } |
| 471 | case <-time.After(runtimeShutdownTimeout): |
| 472 | c.logDebugTiming(runtimeShutdownStart, "CopilotClient.Stop runtime shutdown timed out") |
| 473 | errs = append(errs, fmt.Errorf("timed out gracefully shutting down runtime after %s", runtimeShutdownTimeout)) |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | // The runtime completes all cleanup before responding to runtime.shutdown |
| 478 | // and then leaves termination to us; it deliberately keeps its JSON-RPC |
| 479 | // server alive to send the response and never self-exits. Waiting for a |
| 480 | // self-exit that will never come just wastes time, so terminate the child |
| 481 | // immediately and only wait to reap it. |
| 482 | if c.process != nil && !c.isExternalServer { |
| 483 | if err := c.killProcessAndWait(); err != nil { |
| 484 | errs = append(errs, err) |
| 485 | } |
| 486 | } |
| 487 | c.process = nil |