
for internal decisions as this error is strongly used by other packages. By checking such wrapped errors the internal decision can be influenced accidentally. In this case the retry mechanism checked if the error is context.DeadlineExceeded and assumed it would be created by the internal context. This assumption was wrong.
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
|
|
}
|