Skip to content

Add a zstd decompression window limit - #3710

Draft
pjfanning wants to merge 2 commits into
apache:mainfrom
pjfanning:zstd-window-log-max
Draft

pjfanning wants to merge 2 commits into
apache:mainfrom
pjfanning:zstd-window-log-max

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Motivation

ZstdDecompressor creates a bare ZstdDecompressCtx. libzstd allocates the window buffer from the frame header before any decompressed-size limit applies, so a small input can make the decoder allocate up to 128 MiB per stream. Server-side users such as pekko-http's zstd content-coding need a lower limit; RFC 9659 caps it at a window log of 23 (8 MiB).

zstd-jni's ZstdDecompressCtx.setLongMax (luben/zstd-jni#431) sets that limit, but no zstd-jni release includes it yet (the latest is 1.5.7-24). This PR adds the setting for 2.0.0, so finishing the work later only needs a dependency bump.

Modification

  • ZstdDecompressionSettings.withWindowLogMax(Int) and windowLogMax. 0, the default, keeps libzstd's limit. This mirrors ZstdCompressionSettings.withWindowLog, and the validation range is the same.
  • ZstdDecompressor looks up setLongMax reflectively, because zstd-jni is an optional dependency that users add themselves. If the method is missing, a configured limit fails materialization with an UnsupportedOperationException rather than being silently ignored. The default path is unchanged.
  • Document the option, its zstd-jni requirement and the RFC 9659 limit on the zstdDecompress page.

Result

On 2.0.0, users can configure the window limit, and it takes effect as soon as they use a zstd-jni release with setLongMax. The follow-up (2.0.1) bumps zstdJniVersion and replaces the reflection with a direct call; there is a TODO at the lookup. The new test already covers the real path: it checks whether the method exists, and once it does, it asserts that a 2^24-window frame is rejected at 23 and decodes at 24.

The settings class and the zstd API are new in 2.0.0 and @ApiMayChange, so there is no binary compatibility impact. ZstdDecompressor is @InternalApi.

Tests

  • JDK 17: sbt "stream-tests/testOnly org.apache.pekko.stream.io.compression.ZstdSpec org.apache.pekko.stream.io.compression.CompressionWithoutZstdSpec" with zstd-jni 1.5.7-24: 42 passed. The new window test exercised the UnsupportedOperationException branch.
  • sbt stream/scalafmt stream-tests/Test/scalafmt
  • Not run locally: Scala 3 compile and MiMa, left to CI.

References

Refs #3674

Motivation:
ZstdDecompressor creates a bare ZstdDecompressCtx, so a small frame can make
it allocate a window of up to 128 MiB before any data is decompressed.
zstd-jni's ZstdDecompressCtx.setLongMax (luben/zstd-jni#431) lets the window
be limited, but no zstd-jni release has it yet.

Modification:
Add ZstdDecompressionSettings.withWindowLogMax, where 0 keeps the libzstd
limit, mirroring ZstdCompressionSettings.withWindowLog. ZstdDecompressor looks
up setLongMax reflectively, because zstd-jni is an optional dependency that
users supply. Without it, a configured limit fails materialization with an
UnsupportedOperationException instead of being ignored. Document the option
and the RFC 9659 limit.

Result:
Users can set the window limit now, and it takes effect as soon as they use a
zstd-jni release with setLongMax. A later change can bump zstd-jni and call
the method directly.

Tests:
- sbt "stream-tests/testOnly org.apache.pekko.stream.io.compression.ZstdSpec
  org.apache.pekko.stream.io.compression.CompressionWithoutZstdSpec" with
  zstd-jni 1.5.7-24: 42 passed

References:
Refs apache#3674
@pjfanning pjfanning added this to the 2.0.0-M5 milestone Oct 25, 2026
@He-Pin
He-Pin requested a balanced review from Copilot October 26, 2026 04:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Motivation:
Review feedback on apache#3710 asked for a MethodHandle instead of reflection.

Modification:
Look up ZstdDecompressCtx.setLongMax with MethodHandles.publicLookup and
call it with invokeExact. Exceptions from setLongMax are no longer wrapped,
so the InvocationTargetException unwrapping is gone.

Result:
The optional zstd-jni method is called through a MethodHandle.

Tests:
- sbt "stream-tests/testOnly org.apache.pekko.stream.io.compression.ZstdSpec
  org.apache.pekko.stream.io.compression.CompressionWithoutZstdSpec" with
  zstd-jni 1.5.7-24: 42 passed
- sbt "++3.3.8" stream/compile

References:
Refs apache#3674
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants