RFR [14] JDK-8225763 Inflater and Deflater should implement AutoCloseable

Jaikiran Pai jai.forums2013 at gmail.com
Sat Jul 13 14:18:04 UTC 2019


Hello Peter,

On 13/07/19 2:50 PM, Peter Levart wrote:
> Hi Jaikiran,
>
> On 7/12/19 9:07 AM, Jaikiran Pai wrote:
>> The new updated webrev is at
>> http://cr.openjdk.java.net/~jpai/webrev/8225763/4/webrev/
>
>
> I don't know if there are any custom subclasses of Inflater/Deflater
> around and what they do, but hypothetically consider a subclass of
> Deflater similar to this:
>
> public class MyDeflater extends Deflater {
>     private final Object lock = new Object();
>
>     @Override
>     public void finish() {
>         synchronized (lock) {
>             super.finish();
>             // ... my code ...
>         }
>     }
>
>     @Override
>     public int deflate(byte[] output, int off, int len, int flush) {
>         synchronized (lock) {
>             // ... my code ...
>             int result = super.deflate(output, off, len, flush);
>             // ... my code ...
>             return result;
>         }
>     }
>
>     @Override
>     public int deflate(ByteBuffer output, int flush) {
>         synchronized (lock) {
>             // ... my code ...
>             int result = super.deflate(output, flush);
>             // ... my code ...
>             return result;
>         }
>     }
>
>     @Override
>     public void reset() {
>         synchronized (lock) {
>             super.reset();
>             // ... my code ...
>         }
>     }
>
>     @Override
>     public void end() {
>         synchronized (lock) {
>             super.end();
>             // ... my code ...
>         }
>     }
> }
>
>
> Currently, there's no problem with this subclass as Deflater never
> calls no overridable method while holding internal lock (zsref). The
> newly added close() method is different. It calls end() while holding
> a lock (zsref). Suppose that close() is called from one thread, while
> at the same time some other overridden method is called from another
> thread. Deadlock may happen.

I'm really glad you brought this up. When I added this end() within the
synchronized block of close(), I had the same question in mind and was
wondering if it's something that we should consider. But I never pursued
it enough to actual come up with a detailed analysis like the one you
have done. Thank you for this.


>
>
> In your patch you are trying hard to prevent calling end() from
> close() if end() has already been called (directly or indirectly
> through close()) because a hypothetical subclass of Deflater that
> overrides end() might not be prepared for end() to be called multiple
> times (i.e. there was no specification requiring end() to be
> idempotent). But even in your implementation, there is a concurrent
> race possible that may allow the overridden end() method to be entered
> twice. Consider the following scenario:
>
> Thread A: calls end() explicitly
>
> Thread B: calls close()
>
> There's nothing preventing Thread B from entering overridden part of
> end() while thread A is executing this same part of code. Threads
> synchronize only at the point where the overridden end() calls
> super.end():
>
> public class MyDeflater extends Deflater {
>     @Override
>     public void end() {
>         // ... this part of code may execute concurrently
>         super.end();
>         // ... this part of code may execute concurrently
>     }
>
Good point. Agreed and it does defeat the code that tries to avoid
having end() invoked more than once.


> Well, the subclass may have it's own synchronization in place if it is
> used in a concurrent scenario, like in above hypothetical example:
>
>     @Override
>     public void end() {
>         synchronized (lock) {
>             // ... this part of code may execute multiple times
>             super.end();
>             // ... this part of code may execute multiple times
>         }
>     }
>
> But such synchronization has the already mentioned dangerous property
> of causing deadlock when close() is called concurrently with end(). In
> addition it doesn't prevent parts of overridden end() method to be
> executed multiple times.
>
> The following implementation of close() is in no way less effective in
> preventing multiple executions of parts of code in overridden end()
> method:
>
>     public void close() {
>         synchronized (zsRef) {
>             // check if already closed
>             if (zsRef.address() == 0) {
>                 return;
>             }
>         }
>         // call end() out of synchronized block to prevent
>         // deadlock situations in concurrent scenarios
>         end();
>     }
>
> ...but it is deadlock safe.
>
> It can be argued that synchronization in Deflater is not meant to
> allow Deflater to be used from multiple threads effectively, because
> its API is not multithread friendly (there are races possible that
> would render such usage inpractical). I think synchronization in
> Deflater is only meant to keep Deflater internal state consistent
> which includes state used in native code. This is important to ensure
> stable operation of VM as inconsistency of state in native code may
> crash the VM. There could be intentional exploits in untrusted code
> that would try to crash the VM. So this is a VM stability measure, not
> an API that allows effective multithreaded usage.

Thank you, I think that answers the question I had (in another thread[1]
before this RFR was raised) about the thread safety expectations of
these classes.


>
> In single-threaded usage (that perhaps is the only practical usage),
> there are not problems with your implementation of close() as are no
> problems with proposed variant above.
>
Right, so it all boils down to the expectation of the usage of these
classes. I will need inputs from others who are more knowledgeable about
these classes, to proceed further.

Thank you very much for your detailed review and inputs.

[1]
http://mail.openjdk.java.net/pipermail/core-libs-dev/2019-June/061117.html

-Jaikiran



More information about the core-libs-dev mailing list