Protect encoder wrapper against concurrent closing PiperOrigin-RevId: 843619313
diff --git a/java/org/brotli/wrapper/enc/EncoderJNI.java b/java/org/brotli/wrapper/enc/EncoderJNI.java index 2f7ef30..ecb722e 100644 --- a/java/org/brotli/wrapper/enc/EncoderJNI.java +++ b/java/org/brotli/wrapper/enc/EncoderJNI.java
@@ -8,11 +8,10 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; import org.brotli.enc.PreparedDictionary; -/** - * JNI wrapper for brotli encoder. - */ +/** JNI wrapper for brotli encoder. */ class EncoderJNI { private static native ByteBuffer nativeCreate(long[] context); private static native void nativePush(long[] context, int length); @@ -72,9 +71,9 @@ protected final long[] context = new long[5]; private final ByteBuffer inputBuffer; private boolean fresh = true; + private AtomicInteger crowdness = new AtomicInteger(0); - Wrapper(int inputBufferSize, int quality, int lgwin, Encoder.Mode mode) - throws IOException { + Wrapper(int inputBufferSize, int quality, int lgwin, Encoder.Mode mode) throws IOException { if (inputBufferSize <= 0) { throw new IOException("buffer size must be positive"); } @@ -92,35 +91,56 @@ this.context[4] = 0; } + private void lock() { + int newCrowdness = crowdness.incrementAndGet(); + if (newCrowdness != 1) { + throw new IllegalStateException("encoder is not idle"); + } + } + + private void unlock() { + crowdness.decrementAndGet(); + } + boolean attachDictionary(ByteBuffer dictionary) { - if (!dictionary.isDirect()) { - throw new IllegalArgumentException("only direct buffers allowed"); + try { + lock(); + if (!dictionary.isDirect()) { + throw new IllegalArgumentException("only direct buffers allowed"); + } + if (context[0] == 0) { + throw new IllegalStateException("brotli decoder is already destroyed"); + } + if (!fresh) { + throw new IllegalStateException("decoding is already started"); + } + return nativeAttachDictionary(context, dictionary); + } finally { + unlock(); } - if (context[0] == 0) { - throw new IllegalStateException("brotli decoder is already destroyed"); - } - if (!fresh) { - throw new IllegalStateException("decoding is already started"); - } - return nativeAttachDictionary(context, dictionary); } void push(Operation op, int length) { - if (length < 0) { - throw new IllegalArgumentException("negative block length"); + try { + lock(); + if (length < 0) { + throw new IllegalArgumentException("negative block length"); + } + if (context[0] == 0) { + throw new IllegalStateException("brotli encoder is already destroyed"); + } + if (!isSuccess() || hasMoreOutput()) { + throw new IllegalStateException("pushing input to encoder in unexpected state"); + } + if (hasRemainingInput() && length != 0) { + throw new IllegalStateException("pushing input to encoder over previous input"); + } + context[1] = op.ordinal(); + fresh = false; + nativePush(context, length); + } finally { + unlock(); } - if (context[0] == 0) { - throw new IllegalStateException("brotli encoder is already destroyed"); - } - if (!isSuccess() || hasMoreOutput()) { - throw new IllegalStateException("pushing input to encoder in unexpected state"); - } - if (hasRemainingInput() && length != 0) { - throw new IllegalStateException("pushing input to encoder over previous input"); - } - context[1] = op.ordinal(); - fresh = false; - nativePush(context, length); } boolean isSuccess() { @@ -144,25 +164,35 @@ } ByteBuffer pull() { - if (context[0] == 0) { - throw new IllegalStateException("brotli encoder is already destroyed"); + try { + lock(); + if (context[0] == 0) { + throw new IllegalStateException("brotli encoder is already destroyed"); + } + if (!isSuccess() || !hasMoreOutput()) { + throw new IllegalStateException("pulling while data is not ready"); + } + fresh = false; + return nativePull(context); + } finally { + unlock(); } - if (!isSuccess() || !hasMoreOutput()) { - throw new IllegalStateException("pulling while data is not ready"); - } - fresh = false; - return nativePull(context); } /** * Releases native resources. */ void destroy() { - if (context[0] == 0) { - throw new IllegalStateException("brotli encoder is already destroyed"); + try { + lock(); + if (context[0] == 0) { + throw new IllegalStateException("brotli encoder is already destroyed"); + } + nativeDestroy(context); + context[0] = 0; + } finally { + unlock(); } - nativeDestroy(context); - context[0] = 0; } } }