RFR: 8168640: (fc) Avoiding AtomicBoolean in FileInput/-OutputStream improves startup
David Holmes
david.holmes at oracle.com
Thu Oct 27 23:13:15 UTC 2016
On 27/10/2016 3:46 PM, Peter Levart wrote:
> Hi David,
>
>
> On 10/27/2016 04:57 AM, David Holmes wrote:
>> On 25/10/2016 10:36 PM, Peter Levart wrote:
>>> 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.
>>
>> Sorry but where exactly is the code referenced above?
>
> It was in the previous webrev in the patched FileInputStream. But the
> webrev has been changed in-place...
Okay - nothing to see then :)
Thanks,
David
> Regards, Peter
>
>>
>> Thanks,
>> David
>>
>>>
>>> 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
>>>
>>>
>
More information about the core-libs-dev
mailing list