75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
61 lines
1.4 KiB
Go
61 lines
1.4 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// IdleTimeoutReader wraps an io.Reader and cancels the associated context
|
|
// if no bytes are successfully read within the specified timeout duration.
|
|
// This detects stalled uploads where the network or source stops transmitting data.
|
|
//
|
|
// When the idle timeout fires, the reader is also closed (if it implements io.Closer)
|
|
// to unblock any goroutine blocked on the underlying Read.
|
|
type IdleTimeoutReader struct {
|
|
reader io.Reader
|
|
timeout time.Duration
|
|
cancel context.CancelCauseFunc
|
|
timer *time.Timer
|
|
}
|
|
|
|
// NewIdleTimeoutReader creates a reader that cancels the context via cancel
|
|
// if Read does not return any bytes for the given timeout duration.
|
|
func NewIdleTimeoutReader(reader io.Reader, timeout time.Duration, cancel context.CancelCauseFunc) *IdleTimeoutReader {
|
|
r := &IdleTimeoutReader{
|
|
reader: reader,
|
|
timeout: timeout,
|
|
cancel: cancel,
|
|
}
|
|
|
|
r.timer = time.AfterFunc(timeout, func() {
|
|
cancel(fmt.Errorf("upload idle timeout: no bytes transmitted for %v", timeout))
|
|
|
|
if closer, ok := reader.(io.Closer); ok {
|
|
_ = closer.Close()
|
|
}
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *IdleTimeoutReader) Read(p []byte) (int, error) {
|
|
n, err := r.reader.Read(p)
|
|
|
|
if n > 0 {
|
|
r.timer.Reset(r.timeout)
|
|
}
|
|
|
|
if err != nil && err != io.EOF {
|
|
r.Stop()
|
|
}
|
|
|
|
return n, err
|
|
}
|
|
|
|
// Stop cancels the idle timer. Must be called when the reader is no longer needed.
|
|
func (r *IdleTimeoutReader) Stop() {
|
|
r.timer.Stop()
|
|
}
|