Repository navigation
fix(render): budget decoder intermediates on top of the decoded image - #14
Conversation
Reviewer's guide (collapsed on small PRs)Reviewer's GuideAdjusts decoder budgeting so its intermediate allocations are accounted for separately from the validated final RGBA8 image, allowing exact-limit images to decode without allowing images beyond the caller's dimensions or decoded-size limits. Sequence diagram for decoder memory budgetingsequenceDiagram
participant Caller
participant Decoder as ImageDecoder
participant Limits as DimensionAndSizeValidation
participant Buffers as DecoderBuffers
participant RGBA as RGBA8Image
Caller->>Decoder: decode_pixels()
Decoder->>Limits: validate_dimensions(width, height)
Limits-->>Decoder: dimensions within max_decoded_image_bytes
Decoder->>Decoder: max_alloc = max_decoded_image_bytes.saturating_mul(3)
Decoder->>Buffers: decode intermediate samples, palette, and rows
Decoder->>RGBA: convert to finished RGBA8 buffer
RGBA-->>Caller: decoded image
Flow diagram for exact decoded-image budget boundariesflowchart LR
Input[Image resource] --> Validate[Validate dimensions and final RGBA8 size]
Validate -->|within caller budget| Budget[Set decoder max_alloc to 3x final size]
Budget --> Decode[Decode intermediate buffers and RGBA8 output]
Decode --> Exact[Exact-limit image succeeds]
Validate -->|exceeds caller budget| Reject[Reject before decoding]
Decode -->|decoder budget exceeded| RejectDecode[Reject with ImageLimitExceeded]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Hi @YangtseSu. Thanks for your PR. I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
The first split only covered the region pixel diff; the decoded image limit boundary test is red on upstream main everywhere (it is what PR linuxdeepin#14 fixes), so the gating suite failed on every branch. Both now run as their own visible, non-gating steps with the reason and the fixing PR inline; the gate is everything else, so branch regressions still fail the run.
Both image decode sites handed the image crate a single budget derived from the *finished* RGBA8 raster, while `Limits::max_alloc` accounts for every allocation the decoder makes - its own sample buffer, palette and row buffers included. A picture whose raster fits the limits therefore still failed with "Memory limit exceeded" before those limits were consulted: - `ImageDecoder::decode` rejected every image whose decode needs more than one raster's worth of memory, which is why `crates/rofd-render/tests/images.rs::decoded_limits_have_exact_and_one_over_boundaries` is red on main (`decoded_limits_have_exact_and_one_over_boundaries`). - `decode_seal_raster` (stamp annotations, i.e. electronic seals) reported a decode error instead of `RasterBudgetExceeded`, and silently dropped seals that are inside the limits. Measured with the real decoder on 16-bit RGBA PNGs: a 6000x6000 seal (137 MiB raster, 274 MiB decoder buffers, both well inside `ResourceLimits::max_decoded_image_bytes` = 400 MiB) failed to decode at the old budget and decodes now; a 14336x14336 one is still rejected. Budget the decoder with three times the finished raster and keep the raster checks as the authority (the pre-decode dimension limit and the post-decode `RasterBudgetExceeded` comparison are unchanged). A decoder holds its own buffer (up to 8 bytes per pixel for 16-bit channels) and the RGBA8 conversion at the same time, so the exact boundary of the caller's budget decodes and the wall stays proportional to it. Tests: `cargo test -p rofd-render --test images` 14/14 (1/14 at 140b936), the new `cairo_stamp.rs::a_large_seal_raster_within_the_document_limits_is_not_called_over_budget` (6000x6000 16-bit seal, generated fixture, 420 KiB, ~11 s) fails before and passes after, and `cargo test -p rofd-render` is green. Verified with `cargo fmt --all -- --check` and `cargo clippy -p rofd-render --all-targets -- -D warnings`.
90b76a0 to
2983cf6
Compare
The image crate allocates its own intermediate buffers on top of the decoded RGBA8 raster, so budgeting the decoder with the raster size alone dropped seals (stamp annotations) that are inside the document limits, and reported a decode error where a raster-budget error is designed. Upstream PR linuxdeepin/rofd#14 carries the fix; this package applies its patch. Binary diffs do not survive format-patch + patch, so the regression test fixture ships as a checked source and prepare() installs it. Rebuilt from the pinned 140b936 tree: prepare() applies the three patches cleanly, the package exports the same 78 symbols (rofd_document_get_page_size_mm included), and the papers-ofd suite passes 12/12 against the packaged library.
Merging the workflow onto the release branch left its trigger on ci/fork-checks, so nothing ran on the branch that actually ships (0 runs for release/0.4.3-fork.1). Point push at release/* and make a release ref run strict: no --skip and no continue-on-error steps, because a release branch carries the fixes (PRs linuxdeepin#14, linuxdeepin#15) that make both upstream-red tests green, so they gate like everything else. Non-release refs, reachable only through workflow_dispatch, keep the loose mode: the two tests run as visible, non-gating steps so a branch's own regressions still fail the run.
|
I have read the CLA Document and I hereby sign the CLA. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: lzwind, YangtseSu The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
…merged Upstream `main` carries the decoder budget and the region tolerance (PRs linuxdeepin#14 and linuxdeepin#15), so neither previously red test is red anywhere any more and the dev-only distinction between strict and loose checking has no purpose left. The ref matrix loses its `strict` field, the two `continue-on-error` steps go, and FORK.md describes the single mode.
…merged Upstream `main` carries the decoder budget and the region tolerance (PRs linuxdeepin#14 and linuxdeepin#15), so neither previously red test is red anywhere any more and the dev-only distinction between strict and loose checking has no purpose left. The ref matrix loses its `strict` field, the two `continue-on-error` steps go, and FORK.md describes the single mode.
…merged Upstream `main` carries the decoder budget and the region tolerance (PRs linuxdeepin#14 and linuxdeepin#15), so neither previously red test is red anywhere any more and the dev-only distinction between strict and loose checking has no purpose left. The ref matrix loses its `strict` field, the two `continue-on-error` steps go, and FORK.md describes the single mode.
Both image decode sites handed the
imagecrate a single budget derived from the finishedRGBA8 raster, while
Limits::max_allocaccounts for every allocation the decoder makes —its own sample buffer, palette and row buffers included. A picture whose raster fits the
limits therefore failed with
Memory limit exceededbefore those limits were consulted.Site 1 —
ImageDecoder::decode(crates/rofd-render/src/images.rs)crates/rofd-render/tests/images.rs::decoded_limits_have_exact_and_one_over_boundariesis redon
main(no CI job runs the suite today):max_decoded_image_bytesbounds the finished RGBA8 image, whichvalidate_dimensionsalreadychecks before decoding; passing that size as the allocator budget rejects an image that is
exactly at its limit.
Site 2 —
decode_seal_raster(crates/rofd-render/src/cairo_renderer.rs)Stamp annotations (electronic seals) rasterize their picture through the same crate with the
default limits,
min(max_decoded_image_bytes, max_raster_bytes)as the budget. Consequences,measured with the real decoder on 16-bit RGBA PNGs:
Err— seal silently droppedErr— droppedRasterBudgetExceededErr "Memory limit exceeded"RasterBudgetExceededErrErr(still bounded)Both limits are documented to bound the finished image, so a seal inside them must render;
today it is dropped without a diagnostic, and a seal outside them reports a decode failure
instead of the designed
RasterBudgetExceeded.Change
Budget the decoder with three times the finished raster, and leave the raster checks as the
authority (the pre-decode dimension limit and the post-decode
RasterBudgetExceededcomparisonare unchanged): a decoder holds its own buffer (up to 8 bytes per pixel for 16-bit channels) and
the RGBA8 conversion (4 bytes per pixel) at the same time, so the exact boundary of the caller's
budget decodes while the wall stays proportional to it.
Verification
cargo test -p rofd-render --test images→ 14/14 (1/14 fails at140b936)cargo test -p rofd-render→ greencargo fmt --all -- --check,cargo clippy -p rofd-render --all-targets -- -D warnings→ cleancrates/rofd-render/tests/cairo_stamp.rs::a_large_seal_raster_within_the_document_limits_is_not_called_over_budgetuses a generated 6000×6000 16-bit RGBA seal (420 KiB, ~11 s): it fails before the change
(the stamp pixel stays page-white) and passes after. The fixture is generated by the few
lines recorded in
crates/rofd-render/tests/fixtures/images/PROVENANCE.md.budget while the RGBA8 path only is on compressible input).