Files
polyphem/internal/runner/inactivity_timer.go
2024-08-12 10:02:36 +02:00

112 lines
2.8 KiB
Go

package runner
import (
"errors"
"github.com/openHPI/poseidon/pkg/dto"
"sync"
"time"
)
// InactivityTimer is a wrapper around a timer that is used to delete a a Runner after some time of inactivity.
type InactivityTimer interface {
// SetupTimeout starts the timeout after a runner gets deleted.
SetupTimeout(duration time.Duration)
// ResetTimeout resets the current timeout so that the runner gets deleted after the time set in Setup from now.
// It does not make an already expired timer run again.
ResetTimeout()
// StopTimeout stops the timeout but does not remove the runner.
StopTimeout()
// TimeoutPassed returns true if the timeout expired and false otherwise.
TimeoutPassed() bool
}
type TimerState uint8
const (
TimerInactive TimerState = 0
TimerRunning TimerState = 1
TimerExpired TimerState = 2
)
var (
ErrorRunnerInactivityTimeout DestroyReason = errors.New("runner inactivity timeout exceeded")
ErrorExecutionTimeout = errors.New("execution timeout exceeded")
)
type InactivityTimerImplementation struct {
timer *time.Timer
duration time.Duration
state TimerState
runner Runner
onDestroy DestroyRunnerHandler
mu sync.Mutex
}
func NewInactivityTimer(runner Runner, onDestroy DestroyRunnerHandler) InactivityTimer {
return &InactivityTimerImplementation{
state: TimerInactive,
runner: runner,
onDestroy: onDestroy,
}
}
func (t *InactivityTimerImplementation) SetupTimeout(duration time.Duration) {
t.mu.Lock()
defer t.mu.Unlock()
// Stop old timer if present.
if t.timer != nil {
t.timer.Stop()
}
if duration == 0 {
t.state = TimerInactive
return
}
t.state = TimerRunning
t.duration = duration
t.timer = time.AfterFunc(duration, func() {
t.mu.Lock()
t.state = TimerExpired
// The timer must be unlocked here already in order to avoid a deadlock with the call to StopTimout in Manager.Return.
t.mu.Unlock()
err := t.onDestroy(t.runner)
if err != nil {
log.WithError(err).WithField(dto.KeyRunnerID, t.runner.ID()).
Warn("Returning runner after inactivity caused an error")
} else {
log.WithField(dto.KeyRunnerID, t.runner.ID()).Info("Returning runner due to inactivity timeout")
}
})
}
func (t *InactivityTimerImplementation) ResetTimeout() {
t.mu.Lock()
defer t.mu.Unlock()
if t.state != TimerRunning {
// The timer has already expired or been stopped. We don't want to restart it.
return
}
if t.timer.Stop() {
t.timer.Reset(t.duration)
} else {
log.Error("Timer is in state running but stopped. This should never happen")
}
}
func (t *InactivityTimerImplementation) StopTimeout() {
t.mu.Lock()
defer t.mu.Unlock()
if t.state != TimerRunning {
return
}
t.timer.Stop()
t.state = TimerInactive
}
func (t *InactivityTimerImplementation) TimeoutPassed() bool {
return t.state == TimerExpired
}