Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-04-17 22:59:24 +00:00
parent 8c62b6de8c
commit bc470c5f78
5 changed files with 58 additions and 0 deletions

View file

@ -5,6 +5,7 @@ import "time"
// TODO(gabriel-samfira): needs owner attribute. // TODO(gabriel-samfira): needs owner attribute.
type Locker interface { type Locker interface {
TryLock(key string) bool TryLock(key string) bool
Lock(key string)
Unlock(key string, remove bool) Unlock(key string, remove bool)
Delete(key string) Delete(key string)
} }

View file

@ -29,6 +29,12 @@ func (k *keyMutex) TryLock(key string) bool {
return keyMux.TryLock() return keyMux.TryLock()
} }
func (k *keyMutex) Lock(key string) {
mux, _ := k.muxes.LoadOrStore(key, &sync.Mutex{})
keyMux := mux.(*sync.Mutex)
keyMux.Lock()
}
func (k *keyMutex) Unlock(key string, remove bool) { func (k *keyMutex) Unlock(key string, remove bool) {
mux, ok := k.muxes.Load(key) mux, ok := k.muxes.Load(key)
if !ok { if !ok {

View file

@ -15,6 +15,15 @@ func TryLock(key string) (bool, error) {
return locker.TryLock(key), nil return locker.TryLock(key), nil
} }
func Lock(key string) {
if locker == nil {
panic("no locker is registered")
}
locker.Lock(key)
}
func Unlock(key string, remove bool) error { func Unlock(key string, remove bool) error {
if locker == nil { if locker == nil {
return fmt.Errorf("no locker is registered") return fmt.Errorf("no locker is registered")

View file

@ -136,6 +136,7 @@ func (w *Worker) Start() (err error) {
slog.DebugContext(w.ctx, "starting scale set worker loops", "scale_set", w.consumerID) slog.DebugContext(w.ctx, "starting scale set worker loops", "scale_set", w.consumerID)
go w.loop() go w.loop()
go w.keepListenerAlive() go w.keepListenerAlive()
go w.handleAutoScale()
return nil return nil
} }
@ -307,3 +308,35 @@ func (w *Worker) keepListenerAlive() {
} }
} }
} }
func (w *Worker) handleAutoScale() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-w.quit:
return
case <-w.ctx.Done():
return
case <-ticker.C:
var desiredRunners uint
if w.scaleSet.DesiredRunnerCount > 0 {
desiredRunners = uint(w.scaleSet.DesiredRunnerCount)
}
targetRunners := min(w.scaleSet.MinIdleRunners+desiredRunners, w.scaleSet.MaxRunners)
currentRunners := uint(len(w.runners))
if currentRunners == targetRunners {
slog.DebugContext(w.ctx, "desired runner count reached", "desired_runners", targetRunners)
continue
}
if currentRunners < targetRunners {
slog.DebugContext(w.ctx, "scaling up", "current_runners", currentRunners, "target_runners", targetRunners)
} else {
slog.DebugContext(w.ctx, "scaling down", "current_runners", currentRunners, "target_runners", targetRunners)
}
}
}
}

View file

@ -8,6 +8,7 @@ import (
runnerErrors "github.com/cloudbase/garm-provider-common/errors" runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params" commonParams "github.com/cloudbase/garm-provider-common/params"
"github.com/cloudbase/garm/locking"
"github.com/cloudbase/garm/params" "github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util/github/scalesets" "github.com/cloudbase/garm/util/github/scalesets"
) )
@ -45,12 +46,16 @@ func (w *Worker) HandleJobsCompleted(jobs []params.ScaleSetJobMessage) error {
Status: commonParams.InstancePendingDelete, Status: commonParams.InstancePendingDelete,
RunnerStatus: params.RunnerTerminated, RunnerStatus: params.RunnerTerminated,
} }
locking.Lock(job.RunnerName)
_, err := w.store.UpdateInstance(w.ctx, job.RunnerName, runnerUpdateParams) _, err := w.store.UpdateInstance(w.ctx, job.RunnerName, runnerUpdateParams)
if err != nil { if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) { if !errors.Is(err, runnerErrors.ErrNotFound) {
locking.Unlock(job.RunnerName, false)
return fmt.Errorf("updating runner %s: %w", job.RunnerName, err) return fmt.Errorf("updating runner %s: %w", job.RunnerName, err)
} }
} }
locking.Unlock(job.RunnerName, false)
} }
return nil return nil
} }
@ -68,14 +73,18 @@ func (w *Worker) HandleJobsStarted(jobs []params.ScaleSetJobMessage) error {
RunnerStatus: params.RunnerActive, RunnerStatus: params.RunnerActive,
} }
locking.Lock(job.RunnerName)
_, err := w.store.UpdateInstance(w.ctx, job.RunnerName, updateParams) _, err := w.store.UpdateInstance(w.ctx, job.RunnerName, updateParams)
if err != nil { if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) { if errors.Is(err, runnerErrors.ErrNotFound) {
slog.InfoContext(w.ctx, "runner not found; handled by some other controller?", "runner_name", job.RunnerName) slog.InfoContext(w.ctx, "runner not found; handled by some other controller?", "runner_name", job.RunnerName)
locking.Unlock(job.RunnerName, true)
continue continue
} }
locking.Unlock(job.RunnerName, false)
return fmt.Errorf("updating runner %s: %w", job.RunnerName, err) return fmt.Errorf("updating runner %s: %w", job.RunnerName, err)
} }
locking.Unlock(job.RunnerName, false)
} }
return nil return nil
} }