feat(uploads): ErrClientRead marks an error from reading the client's body (third review)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,27 @@ func NewIntake(s Store, p Prober, sub Submitter, l Limits, tmpDir string) *Intak
|
|||||||
return &Intake{store: s, prober: p, sub: sub, limits: l, tmpDir: tmpDir}
|
return &Intake{store: s, prober: p, sub: sub, limits: l, tmpDir: tmpDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrClientRead marks an error that came from READING the client's body: a connection that
|
||||||
|
// dropped, a body cut short. It is the sender's to retry, unlike a spool, probe or database
|
||||||
|
// fault, which is ours. It is a marker on the error, not a sentinel that others may wrap:
|
||||||
|
// io.ErrUnexpectedEOF alone is also what a database drop looks like.
|
||||||
|
var ErrClientRead = errors.New("uploads: client body read failed")
|
||||||
|
|
||||||
|
// clientReader remembers the last non-EOF error its source returned, so Accept can tell
|
||||||
|
// a failure reading the client from a failure writing the spool file.
|
||||||
|
type clientReader struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := c.r.Read(p)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
c.err = err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
// busyMessage is what a user is told when the queue is full.
|
// busyMessage is what a user is told when the queue is full.
|
||||||
const busyMessage = "Too many recordings are waiting to be transcribed. Please try again in a few minutes."
|
const busyMessage = "Too many recordings are waiting to be transcribed. Please try again in a few minutes."
|
||||||
|
|
||||||
@@ -86,11 +107,15 @@ func (in *Intake) Accept(ctx context.Context, userID, title string, src io.Reade
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Read one byte past the cap so "exactly at the limit" and "over" are told apart.
|
// Read one byte past the cap so "exactly at the limit" and "over" are told apart.
|
||||||
n, err := io.Copy(f, io.LimitReader(src, in.limits.MaxBytes+1))
|
cr := &clientReader{r: io.LimitReader(src, in.limits.MaxBytes+1)}
|
||||||
|
n, err := io.Copy(f, cr)
|
||||||
if cerr := f.Close(); err == nil {
|
if cerr := f.Close(); err == nil {
|
||||||
err = cerr
|
err = cerr
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cr.err != nil && errors.Is(err, cr.err) {
|
||||||
|
return "", fmt.Errorf("%w: %w", ErrClientRead, err)
|
||||||
|
}
|
||||||
return "", fmt.Errorf("uploads: receive: %w", err)
|
return "", fmt.Errorf("uploads: receive: %w", err)
|
||||||
}
|
}
|
||||||
if n > in.limits.MaxBytes {
|
if n > in.limits.MaxBytes {
|
||||||
|
|||||||
Reference in New Issue
Block a user