Hi Peter,

Sorry, I might not understand your use scenario correctly. Let me try :-)

If clean() is invoked via Deflater.end() first, my reading of the Cleaner code suggests that the clean()->ZStreamRef.run() is run by the thread calling deflater.end() directly, not the Cleaner thread. In this scenario, since we are still inside the synchronized(zsRef) block, all other "zsRef" sensitive deflater methods should be blocked by the same synchronzied(zeRef) ? I would assume we don't have problem in this use scenario?


 567     public void end() {
 568         synchronized (zsRef) {
 569             cleanable.clean();
 570             buf = null;
 571         }
 572     }

If some other method is invoked first, for example the setDictionary(...) as in your sample, any direct invocation of delfater.end() from other thread should be blocked because of "synchronized(zsRef)". So I would assume this is not a concern. It seems the concern is that the "Deflater.this" object itself is no longer needed and becomes unreachable after line#261, therefore this deflater becomes phantom-reachable and the ZStreamRef.run() is called by the Cleaner thread, then we have a race condition between the thread calls the setDictionary() and the Cleaner thread? I guess I might
have a wrong assumption here, is it true that if someone/thread is calling
deflater.setDictionary(), that someone/thread should still hold a reference to the deflater and therefore prevent it from becoming phantom-reachable? Or you are saying it is possible under the hotspot optimization the deflater.setDictionary() (and its ensureOpen() included) is being inline-ed and therefore there is no more reference to that deflater even we are still inside that deflater.setDictionary(), so that "after ine#261" scenario
becomes possible?

Thanks,
Sherman


On 9/27/17, 2:31 AM, Peter Levart wrote:
Hi Sherman,

At first I checked the Deflater/Inflater changes. I'll come back with ZipFile later.

I think the code is mostly correct, but I have a concern. If clean() is invoked via Deflater.end(), then the Deflater instance is still alive and synchronization is necessary as other threads might be concurrently invoking other methods. If clean() is invoked via Cleaner thread, then Deflater instance is already phantom reachable and no thread may be accessing it or be in the middle of executing any of its instance methods. How is this guaranteed? Critical Deflater methods synchronize on the zsRef instance, so at least at the beginning of the synchronized block, the Deflater instance is strongly reachable. Take for example the following Deflater instance method:


 254     public void setDictionary(byte[] b, int off, int len) {
 255         if (b == null) {
 256             throw new NullPointerException();
 257         }
 258         if (off < 0 || len < 0 || off > b.length - len) {
 259             throw new ArrayIndexOutOfBoundsException();
 260         }
 261         synchronized (zsRef) {
 262             ensureOpen();
 263             setDictionary(zsRef.address(), b, off, len);
 264         }
 265     }


Up to a point where 'this' is dereferenced to obtain the 'zsRef' value (line 261), the Deflater instance is reachable. But after that, even ensureOpen() may be inlined and 'this' is not needed any more. After that point, obtaining zsRef.address() and calling setDictionaly on the obtained address may be racing with Cleaner thread invoking ZStreamRef.run():

  49     public void run() {
  50         long addr = address;
  51         address = 0;
  52         if (addr != 0) {
  53             end.accept(addr);
  54         }
  55     }


Possible program execution is therefore:

Thread1:

zsRef.address() - returning non-zero address

Thread2:

ZStreamRef.run() - invoking Deflater.end(address) with non-zero address via the passed-in lambda.

Thread1:

setDictionary(<non-zero address from before>, b, off, len)


To fix this, you could sprinkle Reference.reachabilityFence(this) at appropriate places in Deflater, but it is simpler to just add synchronized modifier to ZStreamRef.run() method. There's no danger of deadlock(s) since Cleaner ensures the run() method is invoked at most once.

The same reasoning applies to Inflater to as code is similar.

A nit (Deflater and similar to Inflater):

187 ZStreamRef ref = new ZStreamRef(init(level, DEFAULT_STRATEGY, nowrap),
 188                                         addr -> end(addr));

You could use a method reference there.

Regards, Peter



On 09/27/2017 08:35 AM, Xueming Shen wrote:
Hi,

Please help review the change for JDK-8185582.

issue:    https://bugs.openjdk.java.net/browse/JDK-8185582
webrev: http://cr.openjdk.java.net/~sherman/8185582/webrev/
csr:       https://bugs.openjdk.java.net/browse/JDK-8187485

The proposed change here is to replace the finalize() cleanup mechanism with the newly introduced java.lang.ref.Cleaner for java.util.zip package (Deflater, Inflater, ZipFile and ZipFile.ZipFileInputStrean/ZipFileInflaterInputStream).

Since it's an "incompatible" change, both behavioral and source incompatibility, to remove the public finalize() from these classes, a corresponding CSR is also
created and need review as well.

Thanks,
Sherman



Reply via email to