Improve instance management

This commit is contained in:
2025-07-18 22:39:06 +02:00
parent b0873d5629
commit a6c7b7250f
3 changed files with 94 additions and 17 deletions

View File

@@ -1,12 +1,52 @@
package llamactl package llamactl
import ( import (
"context"
"io"
"os/exec"
"sync"
"github.com/google/uuid" "github.com/google/uuid"
) )
type Instance struct { type Instance struct {
ID uuid.UUID ID uuid.UUID
Port int Args []string
Status string
Options *InstanceOptions Options *InstanceOptions
// Status
Running bool
// Output channels
StdOut chan string // Channel for sending output messages
StdErr chan string // Channel for sending error messages
// internal
cmd *exec.Cmd // Command to run the instance
ctx context.Context // Context for managing the instance lifecycle
cancel context.CancelFunc // Function to cancel the context
stdout io.ReadCloser // Standard output stream
stderr io.ReadCloser // Standard error stream
mu sync.Mutex // Mutex for synchronizing access to the instance
}
func NewInstance(id uuid.UUID, options *InstanceOptions) *Instance {
return &Instance{
ID: id,
Args: options.BuildCommandArgs(),
Options: options,
Running: false,
StdOut: make(chan string, 100),
StdErr: make(chan string, 100),
}
}
func (i *Instance) Start() *exec.Cmd {
args := i.Options.BuildCommandArgs()
cmd := exec.Command("llama-server", args...)
cmd.Start()
return cmd
} }

View File

@@ -46,15 +46,30 @@ func (im *instanceManager) ListInstances() ([]*Instance, error) {
// CreateInstance creates a new instance with the given options and returns it. // CreateInstance creates a new instance with the given options and returns it.
// The instance is initially in a "stopped" state. // The instance is initially in a "stopped" state.
func (im *instanceManager) CreateInstance(options *InstanceOptions) (*Instance, error) { func (im *instanceManager) CreateInstance(options *InstanceOptions) (*Instance, error) {
id := uuid.New() if options == nil {
instance := &Instance{ return nil, fmt.Errorf("instance options cannot be nil")
ID: id,
Port: 8080, // Default port, can be changed later
Status: "stopped", // Initial status
Options: options,
} }
im.instances[id] = instance
// Generate a unique ID for the new instance
id := uuid.New()
for im.instances[id] != nil {
id = uuid.New() // Ensure unique ID
}
// Assign a port if not specified
if options.Port == 0 {
port, err := im.getNextAvailablePort()
if err != nil {
return nil, fmt.Errorf("failed to get next available port: %w", err)
}
options.Port = port
}
instance := NewInstance(id, options)
im.instances[instance.ID] = instance
return instance, nil return instance, nil
} }
// GetInstance retrieves an instance by its ID. // GetInstance retrieves an instance by its ID.
@@ -83,8 +98,8 @@ func (im *instanceManager) DeleteInstance(id uuid.UUID) error {
return fmt.Errorf("instance with ID %s not found", id) return fmt.Errorf("instance with ID %s not found", id)
} }
if im.instances[id].Status != "stopped" { if im.instances[id].Running {
return fmt.Errorf("cannot delete a running instance, stop it first") return fmt.Errorf("instance with ID %s is still running, stop it before deleting", id)
} }
delete(im.instances, id) delete(im.instances, id)
@@ -98,13 +113,11 @@ func (im *instanceManager) StartInstance(id uuid.UUID) (*Instance, error) {
if !exists { if !exists {
return nil, fmt.Errorf("instance with ID %s not found", id) return nil, fmt.Errorf("instance with ID %s not found", id)
} }
if instance.Status == "running" { if instance.Running {
return instance, fmt.Errorf("instance with ID %s is already running", id) return instance, fmt.Errorf("instance with ID %s is already running", id)
} }
//TODO: //TODO:
instance.Status = "running"
return instance, nil return instance, nil
} }
@@ -114,12 +127,11 @@ func (im *instanceManager) StopInstance(id uuid.UUID) (*Instance, error) {
if !exists { if !exists {
return nil, fmt.Errorf("instance with ID %s not found", id) return nil, fmt.Errorf("instance with ID %s not found", id)
} }
if instance.Status == "stopped" { if !instance.Running {
return instance, fmt.Errorf("instance with ID %s is already stopped", id) return instance, fmt.Errorf("instance with ID %s is already stopped", id)
} }
// TODO: // TODO:
instance.Status = "stopped"
return instance, nil return instance, nil
} }

View File

@@ -8,10 +8,35 @@ import (
) )
type InstanceOptions struct { type InstanceOptions struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"` // Display name
// Auto restart
AutoRestart bool `json:"auto_restart,omitempty"`
MaxRestarts int `json:"max_restarts,omitempty"`
RestartDelay int `json:"restart_delay,omitempty"` // in seconds
*LlamaServerOptions *LlamaServerOptions
} }
// UnmarshalJSON implements custom JSON unmarshaling with default values
func (o *InstanceOptions) UnmarshalJSON(data []byte) error {
// Set defaults first
o.AutoRestart = true
o.MaxRestarts = 3
o.RestartDelay = 5
// Create a temporary struct to avoid recursion
type tempInstanceOptions InstanceOptions
temp := (*tempInstanceOptions)(o)
// Unmarshal into the temporary struct
if err := json.Unmarshal(data, temp); err != nil {
return err
}
return nil
}
type LlamaServerOptions struct { type LlamaServerOptions struct {
// Common params // Common params
VerbosePrompt bool `json:"verbose_prompt,omitempty"` VerbosePrompt bool `json:"verbose_prompt,omitempty"`