mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 17:14:28 +00:00
Add onStatusChange callback to instance management for status updates
This commit is contained in:
@@ -82,7 +82,8 @@ type Process struct {
|
|||||||
globalSettings *config.InstancesConfig
|
globalSettings *config.InstancesConfig
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
Status InstanceStatus `json:"status"`
|
Status InstanceStatus `json:"status"`
|
||||||
|
onStatusChange func(oldStatus, newStatus InstanceStatus)
|
||||||
|
|
||||||
// Creation time
|
// Creation time
|
||||||
Created int64 `json:"created,omitempty"` // Unix timestamp when the instance was created
|
Created int64 `json:"created,omitempty"` // Unix timestamp when the instance was created
|
||||||
@@ -193,7 +194,7 @@ func applyDefaultOptions(options *CreateInstanceOptions, globalSettings *config.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewInstance creates a new instance with the given name, log path, and options
|
// NewInstance creates a new instance with the given name, log path, and options
|
||||||
func NewInstance(name string, globalSettings *config.InstancesConfig, options *CreateInstanceOptions) *Process {
|
func NewInstance(name string, globalSettings *config.InstancesConfig, options *CreateInstanceOptions, onStatusChange func(oldStatus, newStatus InstanceStatus)) *Process {
|
||||||
// Validate and copy options
|
// Validate and copy options
|
||||||
optionsCopy := validateAndCopyOptions(name, options)
|
optionsCopy := validateAndCopyOptions(name, options)
|
||||||
// Apply defaults
|
// Apply defaults
|
||||||
@@ -208,6 +209,8 @@ func NewInstance(name string, globalSettings *config.InstancesConfig, options *C
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
timeProvider: realTimeProvider{},
|
timeProvider: realTimeProvider{},
|
||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
|
Status: Stopped,
|
||||||
|
onStatusChange: onStatusChange,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ func TestNewInstance(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
instance := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
|
|
||||||
if instance.Name != "test-instance" {
|
if instance.Name != "test-instance" {
|
||||||
t.Errorf("Expected name 'test-instance', got %q", instance.Name)
|
t.Errorf("Expected name 'test-instance', got %q", instance.Name)
|
||||||
@@ -76,7 +79,10 @@ func TestNewInstance_WithRestartOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
instance := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
opts := instance.GetOptions()
|
opts := instance.GetOptions()
|
||||||
|
|
||||||
// Check that explicit values override defaults
|
// Check that explicit values override defaults
|
||||||
@@ -106,7 +112,10 @@ func TestSetOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, initialOptions)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, initialOptions, mockOnStatusChange)
|
||||||
|
|
||||||
// Update options
|
// Update options
|
||||||
newOptions := &instance.CreateInstanceOptions{
|
newOptions := &instance.CreateInstanceOptions{
|
||||||
@@ -144,7 +153,10 @@ func TestGetProxy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
|
|
||||||
// Get proxy for the first time
|
// Get proxy for the first time
|
||||||
proxy1, err := inst.GetProxy()
|
proxy1, err := inst.GetProxy()
|
||||||
@@ -180,7 +192,10 @@ func TestMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
instance := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
|
|
||||||
data, err := json.Marshal(instance)
|
data, err := json.Marshal(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,7 +318,10 @@ func TestCreateInstanceOptionsValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := instance.NewInstance("test", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
instance := instance.NewInstance("test", globalSettings, options, mockOnStatusChange)
|
||||||
opts := instance.GetOptions()
|
opts := instance.GetOptions()
|
||||||
|
|
||||||
if opts.MaxRestarts == nil {
|
if opts.MaxRestarts == nil {
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ var statusToName = map[InstanceStatus]string{
|
|||||||
|
|
||||||
func (p *Process) SetStatus(status InstanceStatus) {
|
func (p *Process) SetStatus(status InstanceStatus) {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
oldStatus := p.Status
|
||||||
|
|
||||||
p.Status = status
|
p.Status = status
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.onStatusChange != nil {
|
||||||
|
p.onStatusChange(oldStatus, status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) GetStatus() InstanceStatus {
|
func (p *Process) GetStatus() InstanceStatus {
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ func TestUpdateLastRequestTime(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
|
|
||||||
// Test that UpdateLastRequestTime doesn't panic
|
// Test that UpdateLastRequestTime doesn't panic
|
||||||
inst.UpdateLastRequestTime()
|
inst.UpdateLastRequestTime()
|
||||||
@@ -61,7 +64,10 @@ func TestShouldTimeout_NotRunning(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
|
|
||||||
// Instance is not running, should not timeout regardless of configuration
|
// Instance is not running, should not timeout regardless of configuration
|
||||||
if inst.ShouldTimeout() {
|
if inst.ShouldTimeout() {
|
||||||
@@ -85,6 +91,9 @@ func TestShouldTimeout_NoTimeoutConfigured(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
options := &instance.CreateInstanceOptions{
|
options := &instance.CreateInstanceOptions{
|
||||||
IdleTimeout: tt.idleTimeout,
|
IdleTimeout: tt.idleTimeout,
|
||||||
LlamaServerOptions: llamacpp.LlamaServerOptions{
|
LlamaServerOptions: llamacpp.LlamaServerOptions{
|
||||||
@@ -92,7 +101,7 @@ func TestShouldTimeout_NoTimeoutConfigured(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
// Simulate running state
|
// Simulate running state
|
||||||
inst.SetStatus(instance.Running)
|
inst.SetStatus(instance.Running)
|
||||||
|
|
||||||
@@ -116,7 +125,10 @@ func TestShouldTimeout_WithinTimeLimit(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
inst.SetStatus(instance.Running)
|
inst.SetStatus(instance.Running)
|
||||||
|
|
||||||
// Update last request time to now
|
// Update last request time to now
|
||||||
@@ -141,7 +153,10 @@ func TestShouldTimeout_ExceedsTimeLimit(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
inst.SetStatus(instance.Running)
|
inst.SetStatus(instance.Running)
|
||||||
|
|
||||||
// Use MockTimeProvider to simulate old last request time
|
// Use MockTimeProvider to simulate old last request time
|
||||||
@@ -184,7 +199,10 @@ func TestTimeoutConfiguration_Validation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance("test-instance", globalSettings, options)
|
// Mock onStatusChange function
|
||||||
|
mockOnStatusChange := func(oldStatus, newStatus instance.InstanceStatus) {}
|
||||||
|
|
||||||
|
inst := instance.NewInstance("test-instance", globalSettings, options, mockOnStatusChange)
|
||||||
opts := inst.GetOptions()
|
opts := inst.GetOptions()
|
||||||
|
|
||||||
if opts.IdleTimeout == nil || *opts.IdleTimeout != tt.expectedTimeout {
|
if opts.IdleTimeout == nil || *opts.IdleTimeout != tt.expectedTimeout {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type InstanceManager interface {
|
|||||||
type instanceManager struct {
|
type instanceManager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
instances map[string]*instance.Process
|
instances map[string]*instance.Process
|
||||||
runningInstances map[*instance.Process]struct{}
|
runningInstances map[string]struct{}
|
||||||
ports map[int]bool
|
ports map[int]bool
|
||||||
instancesConfig config.InstancesConfig
|
instancesConfig config.InstancesConfig
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ func NewInstanceManager(instancesConfig config.InstancesConfig) InstanceManager
|
|||||||
}
|
}
|
||||||
im := &instanceManager{
|
im := &instanceManager{
|
||||||
instances: make(map[string]*instance.Process),
|
instances: make(map[string]*instance.Process),
|
||||||
runningInstances: make(map[*instance.Process]struct{}),
|
runningInstances: make(map[string]struct{}),
|
||||||
ports: make(map[int]bool),
|
ports: make(map[int]bool),
|
||||||
instancesConfig: instancesConfig,
|
instancesConfig: instancesConfig,
|
||||||
|
|
||||||
@@ -229,8 +229,12 @@ func (im *instanceManager) loadInstance(name, path string) error {
|
|||||||
return fmt.Errorf("instance name mismatch: file=%s, instance.Name=%s", name, persistedInstance.Name)
|
return fmt.Errorf("instance name mismatch: file=%s, instance.Name=%s", name, persistedInstance.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusCallback := func(oldStatus, newStatus instance.InstanceStatus) {
|
||||||
|
im.onStatusChange(persistedInstance.Name, oldStatus, newStatus)
|
||||||
|
}
|
||||||
|
|
||||||
// Create new inst using NewInstance (handles validation, defaults, setup)
|
// Create new inst using NewInstance (handles validation, defaults, setup)
|
||||||
inst := instance.NewInstance(name, &im.instancesConfig, persistedInstance.GetOptions())
|
inst := instance.NewInstance(name, &im.instancesConfig, persistedInstance.GetOptions(), statusCallback)
|
||||||
|
|
||||||
// Restore persisted fields that NewInstance doesn't set
|
// Restore persisted fields that NewInstance doesn't set
|
||||||
inst.Created = persistedInstance.Created
|
inst.Created = persistedInstance.Created
|
||||||
@@ -272,3 +276,14 @@ func (im *instanceManager) autoStartInstances() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (im *instanceManager) onStatusChange(name string, oldStatus, newStatus instance.InstanceStatus) {
|
||||||
|
im.mu.Lock()
|
||||||
|
defer im.mu.Unlock()
|
||||||
|
|
||||||
|
if newStatus == instance.Running {
|
||||||
|
im.runningInstances[name] = struct{}{}
|
||||||
|
} else {
|
||||||
|
delete(im.runningInstances, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,11 @@ func (im *instanceManager) CreateInstance(name string, options *instance.CreateI
|
|||||||
im.ports[options.Port] = true
|
im.ports[options.Port] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
inst := instance.NewInstance(name, &im.instancesConfig, options)
|
statusCallback := func(oldStatus, newStatus instance.InstanceStatus) {
|
||||||
|
im.onStatusChange(name, oldStatus, newStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
inst := instance.NewInstance(name, &im.instancesConfig, options, statusCallback)
|
||||||
im.instances[inst.Name] = inst
|
im.instances[inst.Name] = inst
|
||||||
im.ports[options.Port] = true
|
im.ports[options.Port] = true
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user