Files

68 lines
1.7 KiB
Go

package executor
import (
"context"
"errors"
"os"
"sync"
"time"
log "github.com/sirupsen/logrus"
)
// timeoutContext is a custom context that kills the process group when cancelled due to timeout.
type timeoutContext struct {
context.Context
cancel context.CancelFunc
process *os.Process
executor *Executor
processMu sync.Mutex
}
// newTimeoutContext creates a context that will kill the process group when the timeout expires.
func newTimeoutContext(parent context.Context, timeout time.Duration, executor *Executor) (*timeoutContext, context.CancelFunc) {
ctx, cancel := context.WithTimeout(parent, timeout)
tc := &timeoutContext{
Context: ctx,
cancel: cancel,
executor: executor,
}
// Start a goroutine that kills the process group when the context is cancelled
go func() {
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded {
tc.processMu.Lock()
process := tc.process
tc.processMu.Unlock()
if process != nil {
logEntry := &InternalLogEntry{Process: process}
if err := executor.Kill(logEntry); err != nil {
log.WithFields(log.Fields{
"error": err,
}).Warnf("Failed to kill process group on timeout")
}
}
}
}()
return tc, cancel
}
func (tc *timeoutContext) setProcess(process *os.Process) {
tc.processMu.Lock()
tc.process = process
tc.processMu.Unlock()
// If deadline already expired before process was set, kill now
if errors.Is(tc.Err(), context.DeadlineExceeded) && process != nil {
logEntry := &InternalLogEntry{Process: process}
if err := tc.executor.Kill(logEntry); err != nil {
log.WithFields(log.Fields{
"error": err,
}).Warnf("Failed to kill process group on timeout (late registration)")
}
}
}