Hi Claes,

On 10/25/2016 01:09 PM, Aleksey Shipilev wrote:
On 10/25/2016 12:51 PM, Claes Redestad wrote:
Avoiding AtomicBoolean improves startup and footprint for a sample of
small applications:

Webrev: http://cr.openjdk.java.net/~redestad/8168640/webrev.01/
Bug: https://bugs.openjdk.java.net/browse/JDK-8168640
I would generally disagree to avoid Atomics to improve startup. In this
case, we always lock on close(), even for a short time. I wonder if
using plain Unsafe.compareAndSwap instead of Atomic would be cleaner,
instead of introducing performance bugs with synchronized-s.

Thanks,
-Aleksey



In addition, there is no need for this:

 379                     boolean closed;
 380                     synchronized (closeLock) {
 381                         closed = this.closed;
 382                     }

A simple:

        boolean closed = this.closed;

is equivalent, since this.closed is volatile.

But something else bothers me with this code. There is a race. Here are the relevant parts:

 316     public void close() throws IOException {
 317         if (closed) {
 318             return;
 319         }
 320         synchronized (closeLock) {
 321             if (closed) {
 322                 return;
 323             }
 324             closed = true;
 325         }
 326
 327         FileChannel fc = channel;
 328         if (fc != null) {
 329            fc.close();
 330         }
 331
 332         fd.closeAll(new Closeable() {
 333             public void close() throws IOException {
 334                close0();
 335            }
 336         });
 337     }

and:

 372     public FileChannel getChannel() {
 373         FileChannel fc = this.channel;
 374         if (fc == null) {
 375             synchronized (this) {
 376                 fc = this.channel;
 377                 if (fc == null) {
378 this.channel = fc = FileChannelImpl.open(fd, path, true, false, this);
 379                     boolean closed;
 380                     synchronized (closeLock) {
 381                         closed = this.closed;
 382                     }
 383                     if (closed) {
 384                         try {
 385                             fc.close();
 386                         } catch (IOException ioe) {
387 throw new InternalError(ioe); // should not happen
 388                         }
 389                     }
 390                 }
 391             }
 392         }
 393         return fc;
 394     }


Suppose Thread 1 is executing close() up to line 326, Then Thread 2 kicks-in and executes getChannel() for the 1st time on this FileInputStream. It opens FileChannelImpl and finds closed flag already set, so it closes the channel. Thread 1 then resumes from line 326 and finds 'channel' field already set, so it closes the channel once more.

FileChannelImpl.close() may be idempotent, but why not making sure it is called just once?

In order to fix this, you should read the 'channel' field in close() while holding closeLock and you should publish the 'channel' field in getChannel() while holding closeLock. Like this:

diff -r 2e7a303cd1ec src/java.base/share/classes/java/io/FileInputStream.java --- a/src/java.base/share/classes/java/io/FileInputStream.java Wed Oct 19 11:45:43 2016 +0800 +++ b/src/java.base/share/classes/java/io/FileInputStream.java Tue Oct 25 14:33:05 2016 +0200
@@ -26,7 +26,6 @@
 package java.io;

 import java.nio.channels.FileChannel;
-import java.util.concurrent.atomic.AtomicBoolean;
 import sun.nio.ch.FileChannelImpl;


@@ -60,7 +59,9 @@

     private volatile FileChannel channel;

-    private final AtomicBoolean closed = new AtomicBoolean(false);
+    private final Object closeLock = new Object();
+
+    private volatile boolean closed;

     /**
      * Creates a <code>FileInputStream</code> by
@@ -313,12 +314,18 @@
      * @spec JSR-51
      */
     public void close() throws IOException {
-        if (!closed.compareAndSet(false, true)) {
-            // if compareAndSet() returns false closed was already true
+        if (closed) {
             return;
         }
+        FileChannel fc;
+        synchronized (closeLock) {
+            if (closed) {
+                return;
+            }
+            closed = true;
+            fc = channel;
+        }

-        FileChannel fc = channel;
         if (fc != null) {
            fc.close();
         }
@@ -369,8 +376,13 @@
             synchronized (this) {
                 fc = this.channel;
                 if (fc == null) {
- this.channel = fc = FileChannelImpl.open(fd, path, true, false, this);
-                    if (closed.get()) {
+                    fc = FileChannelImpl.open(fd, path, true, false, this);
+                    boolean closed;
+                    synchronized (closeLock) {
+                        closed = this.closed;
+                        this.channel = fc;
+                    }
+                    if (closed) {
                         try {
                             fc.close();
                         } catch (IOException ioe) {


Regards, Peter


Reply via email to