112 lines
2.8 KiB
Go
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
|
|
}
|