Skip to content

Conversation

@lorban
Copy link
Contributor

@lorban lorban commented Nov 25, 2025

Fixes #14028 by closing the zstd context when sources / sinks are released or when the GC cleans them up.

Also add buffer pooling to the zstd streams.

@lorban lorban self-assigned this Nov 25, 2025
@lorban lorban added the Bug For general bugs on Jetty side label Nov 25, 2025
@lorban lorban requested a review from joakime November 25, 2025 12:58
@lorban lorban moved this to 🏗 In progress in Jetty 12.1.5 Nov 25, 2025
joakime
joakime previously approved these changes Nov 25, 2025
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

Simple enough.

@lorban lorban changed the title Fix ZStandandard native memory leaks Fix ZStandard native memory leaks Nov 25, 2025
…ative resources even when the sink does not do a last write or the source is not read until EOF nor failed

Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
@lorban lorban moved this from 🏗 In progress to 👀 In review in Jetty 12.1.5 Nov 26, 2025
@lorban lorban requested a review from olamy November 26, 2025 21:15
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

I don't like the reduction in recommended default size.

Can we instead have a log warning be produced if the bufferPool isn't setup properly to allow for zstandard lib recommendations?

// but put some upper limit on it for our default buffer size.
// The user can still configure the buffer size to be higher if they want to.
long bufferSizeCeiling = 256_000;
long bufferSizeCeiling = 64 * 1024L; // TODO this is the default ArrayByteBufferPool maxCapacity, there should be a way to discover that
Copy link
Contributor

Choose a reason for hiding this comment

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

This size is smaller than the recommended size by the zstandard lib.

// but put some upper limit on it for our default buffer size.
// The user can still configure the buffer size to be higher if they want to.
long bufferSizeCeiling = 256_000;
long bufferSizeCeiling = 64 * 1024L; // TODO this is the default ArrayByteBufferPool maxCapacity, there should be a way to discover that
Copy link
Contributor

Choose a reason for hiding this comment

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

This size is smaller than the recommended size by the zstandard lib.

@lorban lorban requested a review from sbordet December 1, 2025 17:11
@lorban
Copy link
Contributor Author

lorban commented Dec 1, 2025

@joakime we currently have no API to detect the maximum pooled buffer size, so if we were to warn "please make sure your pool is properly configured" we'd always do it, even when the pool is properly configured.

I hardcoded 64 KB here because the default maximum pooled buffer size is 64 KB, and it is the only solution that is both quick and easy to do that I could think of.

If zstd were to use non-pooled buffer, the performance would go down the drain: each direct byte buffer object has to be finalized (requires enqueuing/dequeuing from a concurrent queue, expensive!) and each time a new buffer is allocated it has to be zero'ed (filling 132 KB of memory with zeros, expensive!).

I agree this solution isn't ideal, but the only alternative I can come up that doesn't require modifying the ByteBufferPool interface with is to change the default ArrayByteBufferPool implementation to pool up to 256 KB buffers, i.e.: the previous ceiling value.

@sbordet @gregw opinions?

… size from its config instead like gzip and brotli already do

Signed-off-by: Ludovic Orban <[email protected]>
@lorban lorban requested a review from joakime December 2, 2025 09:54
@sbordet
Copy link
Contributor

sbordet commented Dec 2, 2025

Reading https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.1.2, the min window size can be just 1 KiB.
However, there is a recommendation for 8 MiB, which seems just huge.

I think it's best for us to stick with a smaller size like 64 KiB, and document the implications with the ByteBufferPool.

…oc ByteBufferPool implications of changing the default

Signed-off-by: Ludovic Orban <[email protected]>
@lorban
Copy link
Contributor Author

lorban commented Dec 2, 2025

So I've reverted to capping the zstd buffer sizes to 64 KB, and I added a comment to the javadoc explaining that this setting has implications on the ByteBufferPool configuration.

I believe this is the most reasonable thing to do for now as a user can always manually set the buffer size.

@lorban lorban requested a review from sbordet December 2, 2025 17:34
Signed-off-by: Ludovic Orban <[email protected]>
@joakime
Copy link
Contributor

joakime commented Dec 2, 2025

The zstandard requirements are Window_Size + 2 * Block_Maximum_Size

Of which, ...

  • Window_Size defaults to 128 KB.
  • Block_Maximum_Size = min(128 KB, Window_Size).

https://github.com/facebook/zstd/wiki/Using-libzstd-in-a-memory-constrained-environment

@joakime
Copy link
Contributor

joakime commented Dec 2, 2025

We use the zstd-jni which is just a Java JNI layer on top of the facebook zstandard lib.
That lib has updated real-world defaults of 128 KB for Window Size.

It recommends using 128KB for the Window Size if dealing with sub 2GB inputs on non-streaming modes (we don't use the streaming modes).
It recommends higher Windows Size of up to 8MB if dealing with inputs over 2GB.

Signed-off-by: Ludovic Orban <[email protected]>
@lorban
Copy link
Contributor Author

lorban commented Dec 3, 2025

@joakime @sbordet I've reverted the code changes related to the zstd buffer size and left the issue as-is for now, with only changes to the javadoc to hint at the problem.

This isn't a regression as this problem exists since the introduction of zstd (de)compression and we need the other fixes to be included in the next release that has to be cut this week, so let's postpone the buffer problem to the next release and simply ignore it for now.

… its own buffer internally and we only use the configured buffer size to control the buffer receiving (de)compressed data + fix bug in continueOp() buffer rollback logic

Signed-off-by: Ludovic Orban <[email protected]>
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

The current PR is invalid for zstandard, and will break it.

Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
@lorban lorban requested a review from joakime December 3, 2025 14:31
joakime
joakime previously approved these changes Dec 3, 2025
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

Good, for now.
We need a new issue to track the ByteBufferPool changes to make it expose it's limits.

@lorban lorban merged commit 03790e9 into jetty-12.1.x Dec 3, 2025
10 checks passed
@github-project-automation github-project-automation bot moved this from 👀 In review to ✅ Done in Jetty 12.1.5 Dec 3, 2025
@lorban lorban deleted the fix/12.1.x/14028-native-leak-zstd branch December 3, 2025 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug For general bugs on Jetty side

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

Native memory leak in zstd compression handler

5 participants