fix(dolt): server mode should support multi-process access
Code review fix: In server mode, Dolt connects to an external sql-server and should NOT be single-process-only. The whole point of server mode is to enable multi-writer access. Changes: - Add Config.GetCapabilities() method that considers server mode - Update daemon_guard, daemon_autostart, daemons, main to use GetCapabilities() - Add TestGetCapabilities test - Update init command help text to document server mode flags The existing CapabilitiesForBackend(string) is kept for backward compatibility but now includes a note to use Config.GetCapabilities() when the full config is available. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,8 @@ func singleProcessOnlyBackend() bool {
|
|||||||
if err != nil || cfg == nil {
|
if err != nil || cfg == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return configfile.CapabilitiesForBackend(cfg.GetBackend()).SingleProcessOnly
|
// Use GetCapabilities() to properly handle Dolt server mode
|
||||||
|
return cfg.GetCapabilities().SingleProcessOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
// shouldAutoStartDaemon checks if daemon auto-start is enabled
|
// shouldAutoStartDaemon checks if daemon auto-start is enabled
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ func guardDaemonStartForDolt(cmd *cobra.Command, _ []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
backend := cfg.GetBackend()
|
// Use GetCapabilities() to properly handle Dolt server mode
|
||||||
if configfile.CapabilitiesForBackend(backend).SingleProcessOnly {
|
if cfg.GetCapabilities().SingleProcessOnly {
|
||||||
return fmt.Errorf("%s", singleProcessBackendHelp(backend))
|
return fmt.Errorf("%s", singleProcessBackendHelp(cfg.GetBackend()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -255,11 +255,12 @@ Stops the daemon gracefully, then starts a new one.`,
|
|||||||
}
|
}
|
||||||
workspace := targetDaemon.WorkspacePath
|
workspace := targetDaemon.WorkspacePath
|
||||||
|
|
||||||
// Guardrail: don't (re)start daemons for single-process backends (e.g., Dolt).
|
// Guardrail: don't (re)start daemons for single-process backends (e.g., embedded Dolt).
|
||||||
// This command may be run from a different workspace, so check the target workspace.
|
// This command may be run from a different workspace, so check the target workspace.
|
||||||
|
// Note: Dolt server mode supports multi-process, so GetCapabilities() is used.
|
||||||
targetBeadsDir := beads.FollowRedirect(filepath.Join(workspace, ".beads"))
|
targetBeadsDir := beads.FollowRedirect(filepath.Join(workspace, ".beads"))
|
||||||
if cfg, err := configfile.Load(targetBeadsDir); err == nil && cfg != nil {
|
if cfg, err := configfile.Load(targetBeadsDir); err == nil && cfg != nil {
|
||||||
if configfile.CapabilitiesForBackend(cfg.GetBackend()).SingleProcessOnly {
|
if cfg.GetCapabilities().SingleProcessOnly {
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
outputJSON(map[string]string{"error": fmt.Sprintf("daemon mode is not supported for backend %q (single-process only)", cfg.GetBackend())})
|
outputJSON(map[string]string{"error": fmt.Sprintf("daemon mode is not supported for backend %q (single-process only)", cfg.GetBackend())})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ to prevent deleted issues from being resurrected during re-initialization.
|
|||||||
With --stealth: configures per-repository git settings for invisible beads usage:
|
With --stealth: configures per-repository git settings for invisible beads usage:
|
||||||
• .git/info/exclude to prevent beads files from being committed
|
• .git/info/exclude to prevent beads files from being committed
|
||||||
• Claude Code settings with bd onboard instruction
|
• Claude Code settings with bd onboard instruction
|
||||||
Perfect for personal use without affecting repo collaborators.`,
|
Perfect for personal use without affecting repo collaborators.
|
||||||
|
|
||||||
|
With --backend dolt --server: configures Dolt to connect to an external dolt sql-server
|
||||||
|
instead of using the embedded driver. This enables multi-writer access for multi-agent
|
||||||
|
environments. Connection settings can be customized with --server-host, --server-port,
|
||||||
|
and --server-user. Password should be set via BEADS_DOLT_PASSWORD environment variable.`,
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
prefix, _ := cmd.Flags().GetString("prefix")
|
prefix, _ := cmd.Flags().GetString("prefix")
|
||||||
quiet, _ := cmd.Flags().GetBool("quiet")
|
quiet, _ := cmd.Flags().GetBool("quiet")
|
||||||
|
|||||||
@@ -623,13 +623,14 @@ var rootCmd = &cobra.Command{
|
|||||||
debug.Logf("wisp operation detected, using direct mode")
|
debug.Logf("wisp operation detected, using direct mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dolt backend (embedded) is single-process-only; never use daemon/RPC.
|
// Embedded Dolt is single-process-only; never use daemon/RPC.
|
||||||
|
// (Dolt server mode supports multi-process and won't trigger this.)
|
||||||
// This must be checked after dbPath is resolved.
|
// This must be checked after dbPath is resolved.
|
||||||
if !noDaemon && singleProcessOnlyBackend() {
|
if !noDaemon && singleProcessOnlyBackend() {
|
||||||
noDaemon = true
|
noDaemon = true
|
||||||
daemonStatus.AutoStartEnabled = false
|
daemonStatus.AutoStartEnabled = false
|
||||||
daemonStatus.FallbackReason = FallbackSingleProcessOnly
|
daemonStatus.FallbackReason = FallbackSingleProcessOnly
|
||||||
daemonStatus.Detail = "backend is single-process-only (dolt): daemon mode disabled; using direct mode"
|
daemonStatus.Detail = "backend is single-process-only (embedded dolt): daemon mode disabled; using direct mode"
|
||||||
debug.Logf("single-process backend detected, using direct mode")
|
debug.Logf("single-process backend detected, using direct mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,17 +176,35 @@ type BackendCapabilities struct {
|
|||||||
|
|
||||||
// CapabilitiesForBackend returns capabilities for a backend string.
|
// CapabilitiesForBackend returns capabilities for a backend string.
|
||||||
// Unknown backends are treated conservatively as single-process-only.
|
// Unknown backends are treated conservatively as single-process-only.
|
||||||
|
//
|
||||||
|
// Note: For Dolt, this returns SingleProcessOnly=true for embedded mode.
|
||||||
|
// Use Config.GetCapabilities() when you have the full config to properly
|
||||||
|
// handle server mode (which supports multi-process access).
|
||||||
func CapabilitiesForBackend(backend string) BackendCapabilities {
|
func CapabilitiesForBackend(backend string) BackendCapabilities {
|
||||||
switch strings.TrimSpace(strings.ToLower(backend)) {
|
switch strings.TrimSpace(strings.ToLower(backend)) {
|
||||||
case "", BackendSQLite:
|
case "", BackendSQLite:
|
||||||
return BackendCapabilities{SingleProcessOnly: false}
|
return BackendCapabilities{SingleProcessOnly: false}
|
||||||
case BackendDolt:
|
case BackendDolt:
|
||||||
|
// Embedded Dolt is single-process-only.
|
||||||
|
// Server mode is handled by Config.GetCapabilities().
|
||||||
return BackendCapabilities{SingleProcessOnly: true}
|
return BackendCapabilities{SingleProcessOnly: true}
|
||||||
default:
|
default:
|
||||||
return BackendCapabilities{SingleProcessOnly: true}
|
return BackendCapabilities{SingleProcessOnly: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCapabilities returns the backend capabilities for this config.
|
||||||
|
// Unlike CapabilitiesForBackend(string), this considers Dolt server mode
|
||||||
|
// which supports multi-process access.
|
||||||
|
func (c *Config) GetCapabilities() BackendCapabilities {
|
||||||
|
backend := c.GetBackend()
|
||||||
|
if backend == BackendDolt && c.IsDoltServerMode() {
|
||||||
|
// Server mode supports multi-writer, so NOT single-process-only
|
||||||
|
return BackendCapabilities{SingleProcessOnly: false}
|
||||||
|
}
|
||||||
|
return CapabilitiesForBackend(backend)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBackend returns the configured backend type, defaulting to SQLite.
|
// GetBackend returns the configured backend type, defaulting to SQLite.
|
||||||
func (c *Config) GetBackend() string {
|
func (c *Config) GetBackend() string {
|
||||||
if c.Backend == "" {
|
if c.Backend == "" {
|
||||||
|
|||||||
@@ -338,6 +338,45 @@ func TestDoltServerMode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGetCapabilities tests that GetCapabilities properly handles server mode
|
||||||
|
func TestGetCapabilities(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantSingleProc bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "sqlite is multi-process",
|
||||||
|
cfg: &Config{Backend: BackendSQLite},
|
||||||
|
wantSingleProc: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dolt embedded is single-process",
|
||||||
|
cfg: &Config{Backend: BackendDolt, DoltMode: DoltModeEmbedded},
|
||||||
|
wantSingleProc: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dolt default (empty) is single-process",
|
||||||
|
cfg: &Config{Backend: BackendDolt},
|
||||||
|
wantSingleProc: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dolt server mode is multi-process",
|
||||||
|
cfg: &Config{Backend: BackendDolt, DoltMode: DoltModeServer},
|
||||||
|
wantSingleProc: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := tt.cfg.GetCapabilities().SingleProcessOnly
|
||||||
|
if got != tt.wantSingleProc {
|
||||||
|
t.Errorf("GetCapabilities().SingleProcessOnly = %v, want %v", got, tt.wantSingleProc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestDoltServerModeRoundtrip tests that server mode config survives save/load
|
// TestDoltServerModeRoundtrip tests that server mode config survives save/load
|
||||||
func TestDoltServerModeRoundtrip(t *testing.T) {
|
func TestDoltServerModeRoundtrip(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|||||||
Reference in New Issue
Block a user