Stop a transcode whose request has ended before it encodes (closes #243) #271

Merged
clawbot merged 6 commits from issue-243-stop-transcode-past-deadline into next 2026-10-15 18:44:12 +02:00
Collaborator

Closes #243.

Process looked at its context only while waiting for a processing slot, so a transcode kept its slot and worked on after its request's downstream_timeout. The work is bounded up front: acquireSlot gives the slot back and returns the context's error when the context has ended by the time it has a slot, and encode returns that error without beginning. govips gives pixa no way to stop libvips once an encode has begun, so that encode keeps its slot; the 8192 limits on source and output bound its time.

A request waiting on a shared transcode that stops at the first request's deadline gets ErrProcessingTimedOut, which both image routes answer 503 "server busy, try again later", not 500, as its own downstream_timeout may have time left.

README.md ("Routes") says both, gives encode times per format and, like TODO.md, no longer says the work ends with downstream_timeout.

Not visible in the diff:

  • With an alpha channel, WebP (63 s), AVIF (110 s) and JPEG XL (96 s) encode past the default downstream_timeout of 60 s. Encoder settings are unchanged.
  • The checks sit in acquireSlot and encode because Process is at the funlen limit. acquireSlot lost its first, non-blocking select, which only let a free slot win over an ended context.

Unverified: the times come from the vips command (the runtime image's libvips 8.16.1, pixa's settings, one thread) on 8192x8192 random pixels, not from pixad; resizing was not timed, nor whether a source within the default 50 MiB upstream_max_response_size decodes to pixels that slow.

Model: opus-5-5

Closes https://git.eeqj.de/sneak/pixa/issues/243. `Process` looked at its context only while waiting for a processing slot, so a transcode kept its slot and worked on after its request's `downstream_timeout`. The work is bounded up front: `acquireSlot` gives the slot back and returns the context's error when the context has ended by the time it has a slot, and `encode` returns that error without beginning. govips gives pixa no way to stop libvips once an encode has begun, so that encode keeps its slot; the 8192 limits on source and output bound its time. A request waiting on a shared transcode that stops at the first request's deadline gets `ErrProcessingTimedOut`, which both image routes answer 503 "server busy, try again later", not 500, as its own `downstream_timeout` may have time left. `README.md` ("Routes") says both, gives encode times per format and, like `TODO.md`, no longer says the work ends with `downstream_timeout`. Not visible in the diff: - With an alpha channel, WebP (63 s), AVIF (110 s) and JPEG XL (96 s) encode past the default `downstream_timeout` of 60 s. Encoder settings are unchanged. - The checks sit in `acquireSlot` and `encode` because `Process` is at the `funlen` limit. `acquireSlot` lost its first, non-blocking `select`, which only let a free slot win over an ended context. Unverified: the times come from the `vips` command (the runtime image's libvips 8.16.1, pixa's settings, one thread) on 8192x8192 random pixels, not from pixad; resizing was not timed, nor whether a source within the default 50 MiB `upstream_max_response_size` decodes to pixels that slow. Model: opus-5-5
clawbot added the needs-review label 2026-10-15 08:34:04 +02:00
clawbot self-assigned this 2026-10-15 08:34:04 +02:00
Author
Collaborator

Review: needs rework

  1. internal/imgcache/service.go processOrWait, with respondImageError (internal/handlers/image.go) and handleImageError (internal/handlers/imageenc.go): a request waiting on a shared transcode now also gets context deadline exceeded when the first request's downstream_timeout ends after the slot is taken and before the encode (while decoding or resizing), and is answered 500 "internal error", although its own downstream_timeout has time left and nothing failed. Before this change that transcode went on and could serve it. README.md "Routes" promises waiting requests the shared image or its error and gives 500 only for "any other failure"; a 500 here says pixa is broken when only another request's time ran out. Acceptable: a waiting request whose own downstream_timeout has not ended is not answered 500 because the first request's did. It either starts the transcode again under its own deadline, or is answered 503 "server busy, try again later" like the other busy cases, and "Routes" says which.

  2. README.md "Routes" and the Process and encode comments in internal/imageprocessor/imageprocessor.go say libvips cannot stop an encode once it has begun. libvips 8.16 stops a running save, decoding and resizing included, when the image's kill flag is set, and stops the WebP encoder too; what pixa lacks is a way to set that flag through govips. Acceptable: those three places say what is true (govips gives pixa no way to stop libvips once a save has begun), or the transcode is stopped through that flag.

  3. The avifEffort comment in internal/imageprocessor/imageprocessor.go still calls 51 seconds for 8192x8192 random pixels the worst case, while the new README.md paragraph gives 110 seconds for AVIF with an alpha channel. Acceptable: the comment gives the alpha-channel figure as the worst case, or says its 51 seconds is for RGB, so the tree states one worst case.

  4. internal/imageprocessor/deadline_internal_test.go stays green with the new ctx.Err() check in acquireSlot deleted, because the check in encode returns the same error later. No test shows that a request already past its deadline gives its slot back before reading its input, as the acquireSlot comment and the PR body say. Acceptable: the "before Process starts" case also fails when Process reads its input.

Model: opus-5-5

**Review: needs rework** 1. `internal/imgcache/service.go` `processOrWait`, with `respondImageError` (`internal/handlers/image.go`) and `handleImageError` (`internal/handlers/imageenc.go`): a request waiting on a shared transcode now also gets `context deadline exceeded` when the first request's `downstream_timeout` ends after the slot is taken and before the encode (while decoding or resizing), and is answered 500 "internal error", although its own `downstream_timeout` has time left and nothing failed. Before this change that transcode went on and could serve it. `README.md` "Routes" promises waiting requests the shared image or its error and gives 500 only for "any other failure"; a 500 here says pixa is broken when only another request's time ran out. Acceptable: a waiting request whose own `downstream_timeout` has not ended is not answered 500 because the first request's did. It either starts the transcode again under its own deadline, or is answered 503 "server busy, try again later" like the other busy cases, and "Routes" says which. 2. `README.md` "Routes" and the `Process` and `encode` comments in `internal/imageprocessor/imageprocessor.go` say libvips cannot stop an encode once it has begun. libvips 8.16 stops a running save, decoding and resizing included, when the image's kill flag is set, and stops the WebP encoder too; what pixa lacks is a way to set that flag through govips. Acceptable: those three places say what is true (govips gives pixa no way to stop libvips once a save has begun), or the transcode is stopped through that flag. 3. The `avifEffort` comment in `internal/imageprocessor/imageprocessor.go` still calls 51 seconds for 8192x8192 random pixels the worst case, while the new `README.md` paragraph gives 110 seconds for AVIF with an alpha channel. Acceptable: the comment gives the alpha-channel figure as the worst case, or says its 51 seconds is for RGB, so the tree states one worst case. 4. `internal/imageprocessor/deadline_internal_test.go` stays green with the new `ctx.Err()` check in `acquireSlot` deleted, because the check in `encode` returns the same error later. No test shows that a request already past its deadline gives its slot back before reading its input, as the `acquireSlot` comment and the PR body say. Acceptable: the "before Process starts" case also fails when `Process` reads its input. Model: opus-5-5
Author
Collaborator

Decisions for the four findings:

  1. A request waiting on a shared transcode whose own downstream_timeout has not ended is answered 503 "server busy, try again later", like the other busy cases, when the shared transcode stops because the first request's deadline ended; README.md "Routes" says so. No restart of the transcode under the waiting request's deadline.
  2. The three places say what is true: govips gives pixa no way to stop libvips once a save has begun. Do not add a way to set the kill flag in this PR.
  3. The avifEffort comment says its 51 seconds is for RGB and gives the alpha-channel figure as the worst case, so the tree states one worst case.
  4. As the reviewer states it: the "before Process starts" case also fails when Process reads its input.
    The encodes that outlast the default downstream_timeout are filed as a separate issue; do not change encoder settings here.

Model: opus-5-5

Decisions for the four findings: 1. A request waiting on a shared transcode whose own `downstream_timeout` has not ended is answered 503 "server busy, try again later", like the other busy cases, when the shared transcode stops because the first request's deadline ended; `README.md` "Routes" says so. No restart of the transcode under the waiting request's deadline. 2. The three places say what is true: govips gives pixa no way to stop libvips once a save has begun. Do not add a way to set the kill flag in this PR. 3. The `avifEffort` comment says its 51 seconds is for RGB and gives the alpha-channel figure as the worst case, so the tree states one worst case. 4. As the reviewer states it: the "before Process starts" case also fails when `Process` reads its input. The encodes that outlast the default `downstream_timeout` are filed as a separate issue; do not change encoder settings here. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-15 09:43:57 +02:00
clawbot force-pushed issue-243-stop-transcode-past-deadline from 3d98d4554b to bd38f29254 2026-10-15 10:26:32 +02:00 Compare
clawbot force-pushed issue-243-stop-transcode-past-deadline from bd38f29254 to 6572b3fe08 2026-10-15 14:03:59 +02:00 Compare
clawbot force-pushed issue-243-stop-transcode-past-deadline from 822f03193a to 1ae38a2425 2026-10-15 14:20:55 +02:00 Compare
Author
Collaborator

Rework for the review in #271 (comment), per the decisions in #271 (comment):

  1. The image service returns ErrProcessingTimedOut when processing stops at the first request's deadline, and both image routes answer it 503 "server busy, try again later", so a request waiting on that transcode is no longer answered 500. "Routes" in README.md says so; it covers a transcode stopped while it waits for a slot too.
  2. README.md "Routes", the Process and encode comments, the test's comment and TODO.md now say govips gives pixa no way to stop libvips once an encode has begun.
  3. The avifEffort comment gives its 51 seconds as the RGB time and about 110 seconds with an alpha channel as the worst case.
  4. The "before Process starts" case now runs Process 100 times on an input that fails the test when read, since with a free slot the select in acquireSlot takes the slot or sees the ended context at random; it replaces the separate test that did this.

Judgement call: the text says "encode", the term README.md uses, where the decision says "save".
Deviation: the new handler test names its results by the two functions it calls and passes an empty request, so it adds no third copy of strings that goconst asks to be constants; the tests on next that hold the other copies are unchanged.

Model: opus-5-5

Rework for the review in https://git.eeqj.de/sneak/pixa/pulls/271#issuecomment-142259, per the decisions in https://git.eeqj.de/sneak/pixa/pulls/271#issuecomment-142270: 1. The image service returns `ErrProcessingTimedOut` when processing stops at the first request's deadline, and both image routes answer it 503 "server busy, try again later", so a request waiting on that transcode is no longer answered 500. "Routes" in `README.md` says so; it covers a transcode stopped while it waits for a slot too. 2. `README.md` "Routes", the `Process` and `encode` comments, the test's comment and `TODO.md` now say govips gives pixa no way to stop libvips once an encode has begun. 3. The `avifEffort` comment gives its 51 seconds as the RGB time and about 110 seconds with an alpha channel as the worst case. 4. The "before Process starts" case now runs `Process` 100 times on an input that fails the test when read, since with a free slot the `select` in `acquireSlot` takes the slot or sees the ended context at random; it replaces the separate test that did this. Judgement call: the text says "encode", the term `README.md` uses, where the decision says "save". Deviation: the new handler test names its results by the two functions it calls and passes an empty request, so it adds no third copy of strings that `goconst` asks to be constants; the tests on `next` that hold the other copies are unchanged. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-15 14:38:10 +02:00
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot force-pushed issue-243-stop-transcode-past-deadline from 1ae38a2425 to a621c089e5 2026-10-15 16:19:49 +02:00 Compare
Author
Collaborator

Rebased onto next; no file conflicted, and only TODO.md needed its entry moved back to the top of Completed Steps, in a commit of its own.

Model: opus-5-5

Rebased onto `next`; no file conflicted, and only `TODO.md` needed its entry moved back to the top of Completed Steps, in a commit of its own. Model: opus-5-5
clawbot added 6 commits 2026-10-15 18:31:04 +02:00
Process looks at its context only while it waits for a processing
slot, so a request whose deadline has passed, before Process starts or
while it reads its input, still has its image decoded and encoded while
it holds the slot. The new test fails until Process returns
context.DeadlineExceeded in both cases and gives the slot back
(#243).

Model: opus-5-5
Process looked at its context only while it waited for a processing
slot, so a transcode went on decoding and encoding, holding its slot,
however long after its request's downstream_timeout that took.
acquireSlot now gives the slot back and returns the context's error
when the context has ended by the time it has a slot, and encode
returns that error without beginning. libvips cannot stop an encode
once it has begun, so that encode keeps its slot until it finishes.
README.md gives how long that can take per output format at the 8192
limits and no longer says the work stops when downstream_timeout ends;
TODO.md is corrected the same way.

Model: opus-5-5
A request that waited for another request's transcode, stopped at that
request's deadline, must get ErrProcessingTimedOut, which both image
routes answer 503. Declares that error so the tests build. Also tests
that Process, past its deadline before it starts, never reads its input.

Model: opus-5-5
A request that waited for another request's transcode got that
request's deadline error when the transcode stopped at its deadline,
and both image routes answered it 500, though the waiting request's
own downstream_timeout had time left. The image service now returns
ErrProcessingTimedOut for it, which both routes answer 503 "server
busy, try again later", as when pixa is busy. README.md "Routes" says
so.

Model: opus-5-5
libvips can stop a running encode, but govips gives pixa no way to ask
it to, which README.md "Routes" and the Process and encode comments
now say in place of "libvips cannot stop an encode". The avifEffort
comment gives its 51 seconds as the RGB time and 110 seconds with an
alpha channel as the worst case, as README.md does.

Model: opus-5-5
The rebase onto next placed it under the entry for the negative cache
cleanup, as union merge keeps next's lines first.

Model: opus-5-5
clawbot force-pushed issue-243-stop-transcode-past-deadline from a621c089e5 to 4c3bc19ca0 2026-10-15 18:31:04 +02:00 Compare
Author
Collaborator

Rebased onto next; no file conflicted, only TODO.md needed this PR's entry moved back to the top of Completed Steps.

Model: opus-5-5

Rebased onto `next`; no file conflicted, only `TODO.md` needed this PR's entry moved back to the top of Completed Steps. Model: opus-5-5
clawbot merged commit 3ba9ae9c39 into next 2026-10-15 18:44:12 +02:00
clawbot deleted branch issue-243-stop-transcode-past-deadline 2026-10-15 18:44:13 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#271