@@ -50,7 +50,7 @@ type App struct {
// global context and cleanup functions
globalCtx context.Context
- cleanupFuncs []func()
+ cleanupFuncs []func() error
}
// New initializes a new applcation instance.
@@ -88,6 +88,9 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
// Initialize LSP clients in the background.
app.initLSPClients(ctx)
+ // cleanup database upon app shutdown
+ app.cleanupFuncs = append(app.cleanupFuncs, conn.Close)
+
// TODO: remove the concept of agent config, most likely.
if cfg.IsConfigured() {
if err := app.InitCoderAgent(); err != nil {
@@ -221,9 +224,10 @@ func (app *App) setupEvents() {
setupSubscriber(ctx, app.serviceEventsWG, "history", app.History.Subscribe, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "mcp", agent.SubscribeMCPEvents, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "lsp", SubscribeLSPEvents, app.events)
- cleanupFunc := func() {
+ cleanupFunc := func() error {
cancel()
app.serviceEventsWG.Wait()
+ return nil
}
app.cleanupFuncs = append(app.cleanupFuncs, cleanupFunc)
}
@@ -297,10 +301,11 @@ func (app *App) Subscribe(program *tea.Program) {
app.tuiWG.Add(1)
tuiCtx, tuiCancel := context.WithCancel(app.globalCtx)
- app.cleanupFuncs = append(app.cleanupFuncs, func() {
+ app.cleanupFuncs = append(app.cleanupFuncs, func() error {
slog.Debug("Cancelling TUI message handler")
tuiCancel()
app.tuiWG.Wait()
+ return nil
})
defer app.tuiWG.Done()
@@ -350,7 +355,9 @@ func (app *App) Shutdown() {
// Call call cleanup functions.
for _, cleanup := range app.cleanupFuncs {
if cleanup != nil {
- cleanup()
+ if err := cleanup(); err != nil {
+ slog.Error("Failed to cleanup app properly on shutdown", "error", err)
+ }
}
}
}
@@ -7,7 +7,8 @@ import (
"log/slog"
"path/filepath"
- _ "github.com/ncruces/go-sqlite3/driver"
+ "github.com/ncruces/go-sqlite3"
+ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/pressly/goose/v3"
@@ -18,17 +19,6 @@ func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
return nil, fmt.Errorf("data.dir is not set")
}
dbPath := filepath.Join(dataDir, "crush.db")
- // Open the SQLite database
- db, err := sql.Open("sqlite3", dbPath)
- if err != nil {
- return nil, fmt.Errorf("failed to open database: %w", err)
- }
-
- // Verify connection
- if err = db.PingContext(ctx); err != nil {
- db.Close()
- return nil, fmt.Errorf("failed to connect to database: %w", err)
- }
// Set pragmas for better performance
pragmas := []string{
@@ -37,14 +27,25 @@ func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
"PRAGMA page_size = 4096;",
"PRAGMA cache_size = -8000;",
"PRAGMA synchronous = NORMAL;",
+ "PRAGMA secure_delete = ON;",
}
- for _, pragma := range pragmas {
- if _, err = db.ExecContext(ctx, pragma); err != nil {
- slog.Error("Failed to set pragma", pragma, err)
- } else {
- slog.Debug("Set pragma", "pragma", pragma)
+ db, err := driver.Open(dbPath, func(c *sqlite3.Conn) error {
+ for _, pragma := range pragmas {
+ if err := c.Exec(pragma); err != nil {
+ return fmt.Errorf("failed to set pragma `%s`: %w", pragma, err)
+ }
}
+ return nil
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to open database: %w", err)
+ }
+
+ // Verify connection
+ if err = db.PingContext(ctx); err != nil {
+ db.Close()
+ return nil, fmt.Errorf("failed to connect to database: %w", err)
}
goose.SetBaseFS(FS)
@@ -58,5 +59,6 @@ func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
slog.Error("Failed to apply migrations", "error", err)
return nil, fmt.Errorf("failed to apply migrations: %w", err)
}
+
return db, nil
}
@@ -4,6 +4,7 @@ import (
"cmp"
"context"
"encoding/json"
+ "errors"
"fmt"
"log/slog"
"maps"
@@ -253,11 +254,15 @@ func updateMCPState(name string, state MCPState, err error, client *client.Clien
}
// CloseMCPClients closes all MCP clients. This should be called during application shutdown.
-func CloseMCPClients() {
+func CloseMCPClients() error {
+ var errs []error
for c := range mcpClients.Seq() {
- _ = c.Close()
+ if err := c.Close(); err != nil {
+ errs = append(errs, err)
+ }
}
mcpBroker.Shutdown()
+ return errors.Join(errs...)
}
var mcpInitRequest = mcp.InitializeRequest{