@@ -7,9 +7,12 @@ import (
77 "context"
88 "encoding/json"
99 "fmt"
10+ "io"
11+ "log"
1012 "os"
1113 "path/filepath"
1214 "strings"
15+ "sync"
1316 "time"
1417
1518 "github.com/mendixlabs/mxcli/mdl/ast"
@@ -18,6 +21,19 @@ import (
1821 "github.com/mendixlabs/mxcli/model"
1922)
2023
24+ // syncWriter wraps an io.Writer with a mutex so concurrent goroutines
25+ // (e.g. background catalog build) can write without racing.
26+ type syncWriter struct {
27+ mu sync.Mutex
28+ w io.Writer
29+ }
30+
31+ func (sw * syncWriter ) Write (p []byte ) (int , error ) {
32+ sw .mu .Lock ()
33+ defer sw .mu .Unlock ()
34+ return sw .w .Write (p )
35+ }
36+
2137// execShowCatalogTables handles SHOW CATALOG TABLES.
2238func execShowCatalogTables (ctx * ExecContext ) error {
2339 // Build catalog if not already built (fast mode by default)
@@ -449,19 +465,29 @@ func execRefreshCatalogStmt(ctx *ExecContext, stmt *ast.RefreshCatalogStmt) erro
449465
450466 // Handle background mode — clone ctx so the goroutine doesn't race
451467 // with the main dispatch loop (which may syncBack and mutate fields).
452- // NOTE: bgCtx.Output still shares the underlying writer with the main
453- // goroutine. This is a pre-existing limitation — the original code also
454- // wrote to ctx.Output from the goroutine. A synchronized writer would
455- // fix this but is out of scope for the executor cleanup.
456468 if stmt .Background {
457- bgCtx := * ctx // shallow copy — isolates scalar fields
458- bgCtx .Cache = nil // detach shared cache so preWarmCache writes stay local
469+ bgCtx := * ctx // shallow copy — isolates scalar fields
470+ bgCtx .Cache = nil // detach shared cache so preWarmCache writes stay local
471+ sw := & syncWriter {w : ctx .Output } // shared mutex-wrapped writer
472+ bgCtx .Output = sw // background goroutine writes through sw
473+ syncCatalog := ctx .SyncCatalog // capture callback before returning
459474 go func () {
460475 if err := buildCatalog (& bgCtx , stmt .Full , stmt .Source ); err != nil {
461476 fmt .Fprintf (bgCtx .Output , "Background catalog build failed: %v\n " , err )
477+ return
478+ }
479+ // Propagate the built catalog back to the Executor so the next
480+ // command picks it up. syncBack has already run for this statement,
481+ // so we use the SyncCatalog callback to write directly to e.catalog.
482+ if bgCtx .Catalog != nil {
483+ if syncCatalog != nil {
484+ syncCatalog (bgCtx .Catalog )
485+ } else {
486+ bgCtx .Catalog .Close ()
487+ }
462488 }
463489 }()
464- fmt .Fprintln (ctx . Output , "Catalog build started in background..." )
490+ fmt .Fprintln (sw , "Catalog build started in background..." )
465491 return nil
466492 }
467493
@@ -618,7 +644,9 @@ func outputCatalogResults(ctx *ExecContext, result *catalog.QueryResult) {
618644 }
619645 tr .Rows = append (tr .Rows , outRow )
620646 }
621- _ = writeResult (ctx , tr )
647+ if err := writeResult (ctx , tr ); err != nil {
648+ log .Printf ("warning: failed to write catalog results: %v" , err )
649+ }
622650}
623651
624652// formatValue formats a value for display.
0 commit comments