Fix default options always overriding updates

This commit is contained in:
2025-07-23 21:49:00 +02:00
parent 59061edb96
commit f2a2e8179e

View File

@@ -28,6 +28,35 @@ type CreateInstanceOptions struct {
LlamaServerOptions `json:",inline"` LlamaServerOptions `json:",inline"`
} }
// UnmarshalJSON implements custom JSON unmarshaling for CreateInstanceOptions
// This is needed because the embedded LlamaServerOptions has its own UnmarshalJSON
// which can interfere with proper unmarshaling of the pointer fields
func (c *CreateInstanceOptions) UnmarshalJSON(data []byte) error {
// First, unmarshal into a temporary struct without the embedded type
type tempCreateOptions struct {
AutoRestart *bool `json:"auto_restart,omitempty"`
MaxRestarts *int `json:"max_restarts,omitempty"`
RestartDelay *int `json:"restart_delay_seconds,omitempty"`
}
var temp tempCreateOptions
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
// Copy the pointer fields
c.AutoRestart = temp.AutoRestart
c.MaxRestarts = temp.MaxRestarts
c.RestartDelay = temp.RestartDelay
// Now unmarshal the embedded LlamaServerOptions
if err := json.Unmarshal(data, &c.LlamaServerOptions); err != nil {
return err
}
return nil
}
// Instance represents a running instance of the llama server // Instance represents a running instance of the llama server
type Instance struct { type Instance struct {
Name string `json:"name"` Name string `json:"name"`
@@ -73,10 +102,7 @@ func validateAndCopyOptions(name string, options *CreateInstanceOptions) *Create
if options.MaxRestarts != nil { if options.MaxRestarts != nil {
maxRestarts := *options.MaxRestarts maxRestarts := *options.MaxRestarts
if maxRestarts > 100 { if maxRestarts < 0 {
log.Printf("Instance %s MaxRestarts value (%d) limited to 100", name, maxRestarts)
maxRestarts = 100
} else if maxRestarts < 0 {
log.Printf("Instance %s MaxRestarts value (%d) cannot be negative, setting to 0", name, maxRestarts) log.Printf("Instance %s MaxRestarts value (%d) cannot be negative, setting to 0", name, maxRestarts)
maxRestarts = 0 maxRestarts = 0
} }
@@ -85,12 +111,9 @@ func validateAndCopyOptions(name string, options *CreateInstanceOptions) *Create
if options.RestartDelay != nil { if options.RestartDelay != nil {
restartDelay := *options.RestartDelay restartDelay := *options.RestartDelay
if restartDelay < 1 { if restartDelay < 0 {
log.Printf("Instance %s RestartDelay value (%d) too low, setting to 5 seconds", name, restartDelay) log.Printf("Instance %s RestartDelay value (%d) cannot be negative, setting to 0 seconds", name, restartDelay)
restartDelay = 5 restartDelay = 0
} else if restartDelay > 300 {
log.Printf("Instance %s RestartDelay value (%d) too high, limiting to 300 seconds", name, restartDelay)
restartDelay = 300
} }
optionsCopy.RestartDelay = &restartDelay optionsCopy.RestartDelay = &restartDelay
} }
@@ -109,10 +132,12 @@ func applyDefaultOptions(options *CreateInstanceOptions, globalSettings *Instanc
defaultAutoRestart := globalSettings.DefaultAutoRestart defaultAutoRestart := globalSettings.DefaultAutoRestart
options.AutoRestart = &defaultAutoRestart options.AutoRestart = &defaultAutoRestart
} }
if options.MaxRestarts == nil { if options.MaxRestarts == nil {
defaultMaxRestarts := globalSettings.DefaultMaxRestarts defaultMaxRestarts := globalSettings.DefaultMaxRestarts
options.MaxRestarts = &defaultMaxRestarts options.MaxRestarts = &defaultMaxRestarts
} }
if options.RestartDelay == nil { if options.RestartDelay == nil {
defaultRestartDelay := globalSettings.DefaultRestartDelay defaultRestartDelay := globalSettings.DefaultRestartDelay
options.RestartDelay = &defaultRestartDelay options.RestartDelay = &defaultRestartDelay
@@ -145,12 +170,10 @@ func (i *Instance) createLogFile() error {
return fmt.Errorf("LogDirectory is empty for instance %s", i.Name) return fmt.Errorf("LogDirectory is empty for instance %s", i.Name)
} }
// Set up instance logs
logPath := i.globalSettings.LogDirectory + "/" + i.Name + ".log" logPath := i.globalSettings.LogDirectory + "/" + i.Name + ".log"
// Store the log file path for later access
i.logFilePath = logPath i.logFilePath = logPath
// Check if directory exists, create if not
if err := os.MkdirAll(i.globalSettings.LogDirectory, 0755); err != nil { if err := os.MkdirAll(i.globalSettings.LogDirectory, 0755); err != nil {
return fmt.Errorf("failed to create log directory: %w", err) return fmt.Errorf("failed to create log directory: %w", err)
} }
@@ -188,6 +211,7 @@ func (i *Instance) GetOptions() *CreateInstanceOptions {
func (i *Instance) SetOptions(options *CreateInstanceOptions) { func (i *Instance) SetOptions(options *CreateInstanceOptions) {
i.mu.Lock() i.mu.Lock()
defer i.mu.Unlock() defer i.mu.Unlock()
if options == nil { if options == nil {
log.Println("Warning: Attempted to set nil options on instance", i.Name) log.Println("Warning: Attempted to set nil options on instance", i.Name)
return return
@@ -262,13 +286,13 @@ func (i *Instance) Start() error {
var err error var err error
i.stdout, err = i.cmd.StdoutPipe() i.stdout, err = i.cmd.StdoutPipe()
if err != nil { if err != nil {
i.closeLogFile() // Ensure log files are closed on error i.closeLogFile()
return fmt.Errorf("failed to get stdout pipe: %w", err) return fmt.Errorf("failed to get stdout pipe: %w", err)
} }
i.stderr, err = i.cmd.StderrPipe() i.stderr, err = i.cmd.StderrPipe()
if err != nil { if err != nil {
i.stdout.Close() // Ensure stdout is closed on error i.stdout.Close()
i.closeLogFile() // Ensure log files are closed on error i.closeLogFile()
return fmt.Errorf("failed to get stderr pipe: %w", err) return fmt.Errorf("failed to get stderr pipe: %w", err)
} }
@@ -321,15 +345,13 @@ func (i *Instance) Stop() error {
i.mu.Unlock() i.mu.Unlock()
// First, try to gracefully stop with SIGINT // Stop the process with SIGINT
if i.cmd.Process != nil { if i.cmd.Process != nil {
if err := i.cmd.Process.Signal(syscall.SIGINT); err != nil { if err := i.cmd.Process.Signal(syscall.SIGINT); err != nil {
log.Printf("Failed to send SIGINT to instance %s: %v", i.Name, err) log.Printf("Failed to send SIGINT to instance %s: %v", i.Name, err)
} }
} }
// Don't call cmd.Wait() here - let the monitor goroutine handle it
// Instead, wait for the monitor to complete or timeout
select { select {
case <-monitorDone: case <-monitorDone:
// Process exited normally // Process exited normally
@@ -352,7 +374,7 @@ func (i *Instance) Stop() error {
} }
} }
i.closeLogFile() // Close log files after stopping i.closeLogFile()
return nil return nil
} }