on main help sign in
perf: improve startup and shutdown speed (#1829)
6f646c9Carlos Alexandro Becker
internal/agent/agent.go+5
@@ -68,6 +68,7 @@ type SessionAgent interface {
68 Run(context.Context, SessionAgentCall) (*fantasy.AgentResult, error)
68 Run(context.Context, SessionAgentCall) (*fantasy.AgentResult, error)
69 SetModels(large Model, small Model)
69 SetModels(large Model, small Model)
70 SetTools(tools []fantasy.AgentTool)
70 SetTools(tools []fantasy.AgentTool)
71+ SetSystemPrompt(systemPrompt string)
71 Cancel(sessionID string)
72 Cancel(sessionID string)
72 CancelAll()
73 CancelAll()
73 IsSessionBusy(sessionID string) bool
74 IsSessionBusy(sessionID string) bool
@@ -964,6 +965,10 @@ func (a *sessionAgent) SetTools(tools []fantasy.AgentTool) {
964 a.tools = tools
965 a.tools = tools
965}
966}
966
967
968+func (a *sessionAgent) SetSystemPrompt(systemPrompt string) {
969+ a.systemPrompt = systemPrompt
970+}
971+
967func (a *sessionAgent) Model() Model {
972func (a *sessionAgent) Model() Model {
968 return a.largeModel
973 return a.largeModel
969}
974}
internal/agent/coordinator.go+11−6
@@ -322,17 +322,12 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
322 return nil, err
322 return nil, err
323 }
323 }
324
324
325− systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), *c.cfg)
326− if err != nil {
327− return nil, err
328− }
329−
330 largeProviderCfg, _ := c.cfg.Providers.Get(large.ModelCfg.Provider)
325 largeProviderCfg, _ := c.cfg.Providers.Get(large.ModelCfg.Provider)
331 result := NewSessionAgent(SessionAgentOptions{
326 result := NewSessionAgent(SessionAgentOptions{
332 large,
327 large,
333 small,
328 small,
334 largeProviderCfg.SystemPromptPrefix,
329 largeProviderCfg.SystemPromptPrefix,
335− systemPrompt,
330+ "",
336 isSubAgent,
331 isSubAgent,
337 c.cfg.Options.DisableAutoSummarize,
332 c.cfg.Options.DisableAutoSummarize,
338 c.permissions.SkipRequests(),
333 c.permissions.SkipRequests(),
@@ -340,6 +335,16 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age
340 c.messages,
335 c.messages,
341 nil,
336 nil,
342 })
337 })
338+
339+ c.readyWg.Go(func() error {
340+ systemPrompt, err := prompt.Build(ctx, large.Model.Provider(), large.Model.Model(), *c.cfg)
341+ if err != nil {
342+ return err
343+ }
344+ result.SetSystemPrompt(systemPrompt)
345+ return nil
346+ })
347+
343 c.readyWg.Go(func() error {
348 c.readyWg.Go(func() error {
344 tools, err := c.buildTools(ctx, agent)
349 tools, err := c.buildTools(ctx, agent)
345 if err != nil {
350 if err != nil {
internal/agent/tools/mcp/init.go+14−15
@@ -107,29 +107,28 @@ func GetState(name string) (ClientInfo, bool) {
107
107
108// Close closes all MCP clients. This should be called during application shutdown.
108// Close closes all MCP clients. This should be called during application shutdown.
109func Close() error {
109func Close() error {
110− var errs []error
111 var wg sync.WaitGroup
110 var wg sync.WaitGroup
111+ done := make(chan struct{}, 1)
112+ go func() {
112− for name, session := range sessions.Seq2() {
113+ for name, session := range sessions.Seq2() {
113− wg.Go(func() {
114+ wg.Go(func() {
114− done := make(chan bool, 1)
115− go func() {
116 if err := session.Close(); err != nil &&
115 if err := session.Close(); err != nil &&
117 !errors.Is(err, io.EOF) &&
116 !errors.Is(err, io.EOF) &&
118 !errors.Is(err, context.Canceled) &&
117 !errors.Is(err, context.Canceled) &&
119 err.Error() != "signal: killed" {
118 err.Error() != "signal: killed" {
120− errs = append(errs, fmt.Errorf("close mcp: %s: %w", name, err))
119+ slog.Warn("Failed to shutdown MCP client", "name", name, "error", err)
121 }
120 }
121+ })
122+ }
123+ wg.Wait()
122− done <- true
124+ done <- struct{}{}
123− }()
125+ }()
124− select {
126+ select {
125− case <-done:
127+ case <-done:
126− case <-time.After(time.Millisecond * 250):
128+ case <-time.After(5 * time.Second):
127− }
128− })
129 }
129 }
130− wg.Wait()
131 broker.Shutdown()
130 broker.Shutdown()
132− return errors.Join(errs...)
131+ return nil
133}
132}
134
133
135// Initialize initializes MCP clients based on the provided configuration.
134// Initialize initializes MCP clients based on the provided configuration.
internal/app/app.go+7−4
@@ -405,12 +405,15 @@ func (app *App) Shutdown() {
405 })
405 })
406
406
407 // Shutdown all LSP clients.
407 // Shutdown all LSP clients.
408+ shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
409+ defer cancel()
408 for name, client := range app.LSPClients.Seq2() {
410 for name, client := range app.LSPClients.Seq2() {
409 wg.Go(func() {
411 wg.Go(func() {
410− shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
411− defer cancel()
412− if err := client.Close(shutdownCtx); err != nil {
412+ if err := client.Close(shutdownCtx); err != nil &&
413+ !errors.Is(err, io.EOF) &&
414+ !errors.Is(err, context.Canceled) &&
415+ err.Error() != "signal: killed" {
413− slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
416+ slog.Warn("Failed to shutdown LSP client", "name", name, "error", err)
414 }
417 }
415 })
418 })
416 }
419 }
internal/lsp/client.go−4
@@ -153,10 +153,6 @@ func (c *Client) Initialize(ctx context.Context, workspaceDir string) (*protocol
153
153
154// Close closes the LSP client.
154// Close closes the LSP client.
155func (c *Client) Close(ctx context.Context) error {
155func (c *Client) Close(ctx context.Context) error {
156− // Try to close all open files first
157− ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
158− defer cancel()
159−
160 c.CloseAllFiles(ctx)
156 c.CloseAllFiles(ctx)
161
157
162 // Shutdown and exit the client
158 // Shutdown and exit the client
internal/shell/background.go+19−7
@@ -4,6 +4,7 @@ import (
4 "bytes"
4 "bytes"
5 "context"
5 "context"
6 "fmt"
6 "fmt"
7+ "slices"
7 "sync"
8 "sync"
8 "sync/atomic"
9 "sync/atomic"
9 "time"
10 "time"
@@ -163,15 +164,26 @@ func (m *BackgroundShellManager) Cleanup() int {
163
164
164// KillAll terminates all background shells.
165// KillAll terminates all background shells.
165func (m *BackgroundShellManager) KillAll() {
166func (m *BackgroundShellManager) KillAll() {
166− shells := make([]*BackgroundShell, 0, m.shells.Len())
167+ shells := slices.Collect(m.shells.Seq())
167− for shell := range m.shells.Seq() {
168− shells = append(shells, shell)
169− }
170 m.shells.Reset(map[string]*BackgroundShell{})
168 m.shells.Reset(map[string]*BackgroundShell{})
169+ done := make(chan struct{}, 1)
170+ go func() {
171+ var wg sync.WaitGroup
172+ for _, shell := range shells {
173+ wg.Go(func() {
174+ shell.cancel()
175+ <-shell.done
176+ })
177+ }
178+ wg.Wait()
179+ done <- struct{}{}
180+ }()
171
181
172− for _, shell := range shells {
182+ select {
173− shell.cancel()
183+ case <-done:
174− <-shell.done
184+ return
185+ case <-time.After(time.Second * 5):
186+ return
175 }
187 }
176}
188}
177
189
internal/skills/skills.go+1−1
@@ -147,7 +147,7 @@ func Discover(paths []string) []*Skill {
147 slog.Warn("Skill validation failed", "path", path, "error", err)
147 slog.Warn("Skill validation failed", "path", path, "error", err)
148 return nil
148 return nil
149 }
149 }
150− slog.Info("Successfully loaded skill", "name", skill.Name, "path", path)
150+ slog.Debug("Successfully loaded skill", "name", skill.Name, "path", path)
151 mu.Lock()
151 mu.Lock()
152 skills = append(skills, skill)
152 skills = append(skills, skill)
153 mu.Unlock()
153 mu.Unlock()