diff --git a/core-common/src/main/java/org/glassfish/jersey/message/DeflateEncoder.java b/core-common/src/main/java/org/glassfish/jersey/message/DeflateEncoder.java index ce2e4157ba..6bfe9d2e0b 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/DeflateEncoder.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/DeflateEncoder.java @@ -31,6 +31,8 @@ import javax.inject.Inject; +import org.glassfish.jersey.message.internal.ClosingDeflaterOutputStream; +import org.glassfish.jersey.message.internal.ClosingInflaterInputStream; import org.glassfish.jersey.spi.ContentEncoder; /** @@ -77,7 +79,7 @@ public InputStream decode(String contentEncoding, InputStream encodedStream) return new InflaterInputStream(markSupportingStream); } else { // no zlib wrapper - return new InflaterInputStream(markSupportingStream, new Inflater(true)); + return new ClosingInflaterInputStream(markSupportingStream, true); } } @@ -98,7 +100,7 @@ public OutputStream encode(String contentEncoding, OutputStream entityStream) } return deflateWithoutZLib - ? new DeflaterOutputStream(entityStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)) + ? new ClosingDeflaterOutputStream(entityStream, Deflater.DEFAULT_COMPRESSION, true) : new DeflaterOutputStream(entityStream); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingDeflaterOutputStream.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingDeflaterOutputStream.java new file mode 100644 index 0000000000..931ae6007f --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingDeflaterOutputStream.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.message.internal; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** + * After Java 9, in the class Deflater the method finalize doesn't call end anymore + * The method end must be called explicitly. But when using the constructor + * DeflaterOutputStream(OutputStream out, Deflater def), the Deflater.end() method will + * not be called when closing the stream. + * This lead to memory leaks in the off-heap. + * + * @see java.util.zip.Deflater + * @see java.util.zip.DeflaterOutputStream + */ +public final class ClosingDeflaterOutputStream extends OutputStream { + + private final Deflater deflater; + + private final DeflaterOutputStream delegate; + + private boolean closed = false; + + public ClosingDeflaterOutputStream(OutputStream out, int level, boolean nowrap) { + deflater = new Deflater(level, nowrap); + delegate = new DeflaterOutputStream(out, deflater); + } + + @Override + public void write(int b) throws IOException { + delegate.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + delegate.write(b, off, len); + } + + public void finish() throws IOException { + delegate.finish(); + } + + @Override + public void close() throws IOException { + if (!closed) { + try { + delegate.close(); + } finally { + deflater.end(); + } + closed = true; + } + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + delegate.write(b); + } + +} diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingInflaterInputStream.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingInflaterInputStream.java new file mode 100644 index 0000000000..cccdea8307 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ClosingInflaterInputStream.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.message.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + * After Java 9, in the class Inflater the method finalize doesn't call end anymore + * The method end must be called explicitly. But when using the constructor + * InflaterInputStream(InputStream out, Inflater def), the Inflater.end() method will + * not be called when closing the stream. + * This lead to memory leaks in the off-heap. + * + * @see java.util.zip.Inflater + * @see java.util.zip.InflaterInputStream + */ +public class ClosingInflaterInputStream extends InputStream { + + private final Inflater inflater; + + private final InflaterInputStream delegate; + + private boolean closed = false; + + public ClosingInflaterInputStream(InputStream inputStream, boolean nowrap) { + inflater = new Inflater(nowrap); + delegate = new InflaterInputStream(inputStream, inflater); + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public int available() throws IOException { + return delegate.available(); + } + + @Override + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + @Override + public void close() throws IOException { + if (!closed) { + inflater.end(); + delegate.close(); + closed = true; + } + } + + @Override + public boolean markSupported() { + return delegate.markSupported(); + } + + @Override + public void mark(int readlimit) { + delegate.mark(readlimit); + } + + @Override + public void reset() throws IOException { + delegate.reset(); + } + + @Override + public int read(byte[] b) throws IOException { + return delegate.read(b); + } + +}