mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
Refactor instance status management: replace Running boolean with InstanceStatus enum and update related methods
This commit is contained in:
@@ -82,7 +82,7 @@ type Process struct {
|
||||
globalSettings *config.InstancesConfig
|
||||
|
||||
// Status
|
||||
Running bool `json:"running"`
|
||||
Status InstanceStatus `json:"status"`
|
||||
|
||||
// Creation time
|
||||
Created int64 `json:"created,omitempty"` // Unix timestamp when the instance was created
|
||||
@@ -287,12 +287,12 @@ func (i *Process) MarshalJSON() ([]byte, error) {
|
||||
temp := struct {
|
||||
Name string `json:"name"`
|
||||
Options *CreateInstanceOptions `json:"options,omitempty"`
|
||||
Running bool `json:"running"`
|
||||
Status InstanceStatus `json:"status"`
|
||||
Created int64 `json:"created,omitempty"`
|
||||
}{
|
||||
Name: i.Name,
|
||||
Options: i.options,
|
||||
Running: i.Running,
|
||||
Status: i.Status,
|
||||
Created: i.Created,
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ func (i *Process) UnmarshalJSON(data []byte) error {
|
||||
temp := struct {
|
||||
Name string `json:"name"`
|
||||
Options *CreateInstanceOptions `json:"options,omitempty"`
|
||||
Running bool `json:"running"`
|
||||
Status InstanceStatus `json:"status"`
|
||||
Created int64 `json:"created,omitempty"`
|
||||
}{}
|
||||
|
||||
@@ -315,7 +315,7 @@ func (i *Process) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// Set the fields
|
||||
i.Name = temp.Name
|
||||
i.Running = temp.Running
|
||||
i.Status = temp.Status
|
||||
i.Created = temp.Created
|
||||
|
||||
// Handle options with validation but no defaults
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestNewInstance(t *testing.T) {
|
||||
if instance.Name != "test-instance" {
|
||||
t.Errorf("Expected name 'test-instance', got %q", instance.Name)
|
||||
}
|
||||
if instance.Running {
|
||||
if instance.IsRunning() {
|
||||
t.Error("New instance should not be running")
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestMarshalJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check that JSON contains expected fields
|
||||
var result map[string]interface{}
|
||||
var result map[string]any
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("JSON unmarshal failed: %v", err)
|
||||
@@ -197,8 +197,8 @@ func TestMarshalJSON(t *testing.T) {
|
||||
if result["name"] != "test-instance" {
|
||||
t.Errorf("Expected name 'test-instance', got %v", result["name"])
|
||||
}
|
||||
if result["running"] != false {
|
||||
t.Errorf("Expected running false, got %v", result["running"])
|
||||
if result["status"] != "stopped" {
|
||||
t.Errorf("Expected status 'stopped', got %v", result["status"])
|
||||
}
|
||||
|
||||
// Check that options are included
|
||||
@@ -218,7 +218,7 @@ func TestMarshalJSON(t *testing.T) {
|
||||
func TestUnmarshalJSON(t *testing.T) {
|
||||
jsonData := `{
|
||||
"name": "test-instance",
|
||||
"running": true,
|
||||
"status": "running",
|
||||
"options": {
|
||||
"model": "/path/to/model.gguf",
|
||||
"port": 8080,
|
||||
@@ -236,8 +236,8 @@ func TestUnmarshalJSON(t *testing.T) {
|
||||
if inst.Name != "test-instance" {
|
||||
t.Errorf("Expected name 'test-instance', got %q", inst.Name)
|
||||
}
|
||||
if !inst.Running {
|
||||
t.Error("Expected running to be true")
|
||||
if !inst.IsRunning() {
|
||||
t.Error("Expected status to be running")
|
||||
}
|
||||
|
||||
opts := inst.GetOptions()
|
||||
|
||||
@@ -11,18 +11,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (i *Process) IsRunning() bool {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
return i.Running
|
||||
}
|
||||
|
||||
// Start starts the llama server instance and returns an error if it fails.
|
||||
func (i *Process) Start() error {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
if i.Running {
|
||||
if i.IsRunning() {
|
||||
return fmt.Errorf("instance %s is already running", i.Name)
|
||||
}
|
||||
|
||||
@@ -71,7 +65,7 @@ func (i *Process) Start() error {
|
||||
return fmt.Errorf("failed to start instance %s: %w", i.Name, err)
|
||||
}
|
||||
|
||||
i.Running = true
|
||||
i.SetStatus(Running)
|
||||
|
||||
// Create channel for monitor completion signaling
|
||||
i.monitorDone = make(chan struct{})
|
||||
@@ -88,7 +82,7 @@ func (i *Process) Start() error {
|
||||
func (i *Process) Stop() error {
|
||||
i.mu.Lock()
|
||||
|
||||
if !i.Running {
|
||||
if !i.IsRunning() {
|
||||
// Even if not running, cancel any pending restart
|
||||
if i.restartCancel != nil {
|
||||
i.restartCancel()
|
||||
@@ -105,8 +99,8 @@ func (i *Process) Stop() error {
|
||||
i.restartCancel = nil
|
||||
}
|
||||
|
||||
// Set running to false first to signal intentional stop
|
||||
i.Running = false
|
||||
// Set status to stopped first to signal intentional stop
|
||||
i.SetStatus(Stopped)
|
||||
|
||||
// Clean up the proxy
|
||||
i.proxy = nil
|
||||
@@ -151,7 +145,7 @@ func (i *Process) Stop() error {
|
||||
}
|
||||
|
||||
func (i *Process) WaitForHealthy(timeout int) error {
|
||||
if !i.Running {
|
||||
if !i.IsRunning() {
|
||||
return fmt.Errorf("instance %s is not running", i.Name)
|
||||
}
|
||||
|
||||
@@ -233,12 +227,12 @@ func (i *Process) monitorProcess() {
|
||||
i.mu.Lock()
|
||||
|
||||
// Check if the instance was intentionally stopped
|
||||
if !i.Running {
|
||||
if !i.IsRunning() {
|
||||
i.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
i.Running = false
|
||||
i.SetStatus(Stopped)
|
||||
i.logger.Close()
|
||||
|
||||
// Cancel any existing restart context since we're handling a new exit
|
||||
|
||||
72
pkg/instance/status.go
Normal file
72
pkg/instance/status.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Enum for instance status
|
||||
type InstanceStatus int
|
||||
|
||||
const (
|
||||
Stopped InstanceStatus = iota
|
||||
Running
|
||||
Failed
|
||||
)
|
||||
|
||||
var nameToStatus = map[string]InstanceStatus{
|
||||
"stopped": Stopped,
|
||||
"running": Running,
|
||||
"failed": Failed,
|
||||
}
|
||||
|
||||
var statusToName = map[InstanceStatus]string{
|
||||
Stopped: "stopped",
|
||||
Running: "running",
|
||||
Failed: "failed",
|
||||
}
|
||||
|
||||
func (p *Process) SetStatus(status InstanceStatus) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.Status = status
|
||||
}
|
||||
|
||||
func (p *Process) GetStatus() InstanceStatus {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.Status
|
||||
}
|
||||
|
||||
// IsRunning returns true if the status is Running
|
||||
func (p *Process) IsRunning() bool {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.Status == Running
|
||||
}
|
||||
|
||||
func (s InstanceStatus) MarshalJSON() ([]byte, error) {
|
||||
name, ok := statusToName[s]
|
||||
if !ok {
|
||||
name = "stopped" // Default to "stopped" for unknown status
|
||||
}
|
||||
return json.Marshal(name)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler
|
||||
func (s *InstanceStatus) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status, ok := nameToStatus[str]
|
||||
if !ok {
|
||||
log.Printf("Unknown instance status: %s", str)
|
||||
status = Stopped // Default to Stopped on unknown status
|
||||
}
|
||||
|
||||
*s = status
|
||||
return nil
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func (i *Process) ShouldTimeout() bool {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
if !i.Running || i.options.IdleTimeout == nil || *i.options.IdleTimeout <= 0 {
|
||||
if !i.IsRunning() || i.options.IdleTimeout == nil || *i.options.IdleTimeout <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestShouldTimeout_NoTimeoutConfigured(t *testing.T) {
|
||||
|
||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
||||
// Simulate running state
|
||||
inst.Running = true
|
||||
inst.SetStatus(instance.Running)
|
||||
|
||||
if inst.ShouldTimeout() {
|
||||
t.Errorf("Instance with %s should not timeout", tt.name)
|
||||
@@ -117,7 +117,7 @@ func TestShouldTimeout_WithinTimeLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
||||
inst.Running = true
|
||||
inst.SetStatus(instance.Running)
|
||||
|
||||
// Update last request time to now
|
||||
inst.UpdateLastRequestTime()
|
||||
@@ -142,7 +142,7 @@ func TestShouldTimeout_ExceedsTimeLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
||||
inst.Running = true
|
||||
inst.SetStatus(instance.Running)
|
||||
|
||||
// Use MockTimeProvider to simulate old last request time
|
||||
mockTime := NewMockTimeProvider(time.Now())
|
||||
|
||||
@@ -150,7 +150,7 @@ func (im *instanceManager) Shutdown() {
|
||||
wg.Add(len(im.instances))
|
||||
|
||||
for name, inst := range im.instances {
|
||||
if !inst.Running {
|
||||
if !inst.IsRunning() {
|
||||
wg.Done() // If instance is not running, just mark it as done
|
||||
continue
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (im *instanceManager) loadInstance(name, path string) error {
|
||||
|
||||
// Restore persisted fields that NewInstance doesn't set
|
||||
inst.Created = persistedInstance.Created
|
||||
inst.Running = persistedInstance.Running
|
||||
inst.SetStatus(persistedInstance.Status)
|
||||
|
||||
// Check for port conflicts and add to maps
|
||||
if inst.GetOptions() != nil && inst.GetOptions().Port > 0 {
|
||||
@@ -254,7 +254,7 @@ func (im *instanceManager) autoStartInstances() {
|
||||
im.mu.RLock()
|
||||
var instancesToStart []*instance.Process
|
||||
for _, inst := range im.instances {
|
||||
if inst.Running && // Was running when persisted
|
||||
if inst.IsRunning() && // Was running when persisted
|
||||
inst.GetOptions() != nil &&
|
||||
inst.GetOptions().AutoRestart != nil &&
|
||||
*inst.GetOptions().AutoRestart {
|
||||
@@ -266,7 +266,7 @@ func (im *instanceManager) autoStartInstances() {
|
||||
for _, inst := range instancesToStart {
|
||||
log.Printf("Auto-starting instance %s", inst.Name)
|
||||
// Reset running state before starting (since Start() expects stopped instance)
|
||||
inst.Running = false
|
||||
inst.SetStatus(instance.Stopped)
|
||||
if err := inst.Start(); err != nil {
|
||||
log.Printf("Failed to auto-start instance %s: %v", inst.Name, err)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestCreateInstance_Success(t *testing.T) {
|
||||
if inst.Name != "test-instance" {
|
||||
t.Errorf("Expected instance name 'test-instance', got %q", inst.Name)
|
||||
}
|
||||
if inst.Running {
|
||||
if inst.GetStatus() != instance.Stopped {
|
||||
t.Error("New instance should not be running")
|
||||
}
|
||||
if inst.GetOptions().Port != 8080 {
|
||||
@@ -357,7 +357,7 @@ func TestTimeoutFunctionality(t *testing.T) {
|
||||
inst.SetTimeProvider(mockTime)
|
||||
|
||||
// Set instance to running state so timeout logic can work
|
||||
inst.Running = true
|
||||
inst.SetStatus(instance.Running)
|
||||
|
||||
// Simulate instance being "running" for timeout check (without actual process)
|
||||
// We'll test the ShouldTimeout logic directly
|
||||
@@ -377,7 +377,7 @@ func TestTimeoutFunctionality(t *testing.T) {
|
||||
}
|
||||
|
||||
// Reset running state to avoid shutdown issues
|
||||
inst.Running = false
|
||||
inst.SetStatus(instance.Stopped)
|
||||
|
||||
// Test that instance without timeout doesn't timeout
|
||||
noTimeoutOptions := &instance.CreateInstanceOptions{
|
||||
@@ -393,7 +393,7 @@ func TestTimeoutFunctionality(t *testing.T) {
|
||||
}
|
||||
|
||||
noTimeoutInst.SetTimeProvider(mockTime)
|
||||
noTimeoutInst.Running = true // Set to running for timeout check
|
||||
noTimeoutInst.SetStatus(instance.Running) // Set to running for timeout check
|
||||
noTimeoutInst.UpdateLastRequestTime()
|
||||
|
||||
// Even with time advanced, should not timeout
|
||||
@@ -402,7 +402,7 @@ func TestTimeoutFunctionality(t *testing.T) {
|
||||
}
|
||||
|
||||
// Reset running state to avoid shutdown issues
|
||||
noTimeoutInst.Running = false
|
||||
noTimeoutInst.SetStatus(instance.Stopped)
|
||||
}
|
||||
|
||||
func TestConcurrentAccess(t *testing.T) {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (im *instanceManager) UpdateInstance(name string, options *instance.CreateI
|
||||
}
|
||||
|
||||
// Check if instance is running before updating options
|
||||
wasRunning := instance.Running
|
||||
wasRunning := instance.IsRunning()
|
||||
|
||||
// If the instance is running, stop it first
|
||||
if wasRunning {
|
||||
@@ -147,7 +147,7 @@ func (im *instanceManager) DeleteInstance(name string) error {
|
||||
return fmt.Errorf("instance with name %s not found", name)
|
||||
}
|
||||
|
||||
if instance.Running {
|
||||
if instance.IsRunning() {
|
||||
return fmt.Errorf("instance with name %s is still running, stop it before deleting", name)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (im *instanceManager) StartInstance(name string) (*instance.Process, error)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("instance with name %s not found", name)
|
||||
}
|
||||
if instance.Running {
|
||||
if instance.IsRunning() {
|
||||
return instance, fmt.Errorf("instance with name %s is already running", name)
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func (im *instanceManager) StopInstance(name string) (*instance.Process, error)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("instance with name %s not found", name)
|
||||
}
|
||||
if !instance.Running {
|
||||
if !instance.IsRunning() {
|
||||
return instance, fmt.Errorf("instance with name %s is already stopped", name)
|
||||
}
|
||||
|
||||
|
||||
@@ -451,7 +451,7 @@ func (h *Handler) ProxyToInstance() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if !inst.Running {
|
||||
if !inst.IsRunning() {
|
||||
http.Error(w, "Instance is not running", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
@@ -574,7 +574,7 @@ func (h *Handler) OpenAIProxy() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if !inst.Running {
|
||||
if !inst.IsRunning() {
|
||||
if inst.GetOptions().OnDemandStart != nil && *inst.GetOptions().OnDemandStart {
|
||||
// If on-demand start is enabled, start the instance
|
||||
if _, err := h.InstanceManager.StartInstance(modelName); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user