JDK-8011537 : (fs) Path.register(..) clears interrupt status of thread with no InterruptedException
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 7
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2013-04-04
  • Updated: 2015-05-26
  • Resolved: 2014-05-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u40Fixed 9 b13Fixed
Related Reports
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
java version  " 1.7.0_11 " 
Java(TM) SE Runtime Environment (build 1.7.0_11-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
A thread after interrupted, calls path.register(http://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html#register%28java.nio.file.WatchService,%20java.nio.file.WatchEvent.Kind...%29) method. The interrupt status gets cleared without any InterruptedException being thrown. Resulting in unsuccessful termination of a task in ExecutorService.

The documentation doesn't explain any behavior for this.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the code. You will probably have to try different sleep times to replicate it (at line c). On my computer sleep time of 10 ms replicates the case 99/100 times.



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
interrupted   //line 1
Interrupt Status: true at line a   //line 2
Interrupt Status: true at line b.  //line 3

/*
if line 1 is printed. Then line 2 and line 3 should print true. Which means that the thread was interrupted. In reality, it prints false which means the status is cleared without any exception being thrown.
*/
ACTUAL -
interrupted
Interrupt Status: true at line a
Interrupt Status: false at line b.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.nio.file.*;
import static java.nio.file.LinkOption.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.nio.file.attribute.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Example to watch a directory (or tree) for changes to files.
*/

public class WatchDir {

private final WatchService watcher;
private final Map<WatchKey,Path> keys;
private final boolean recursive;



/**
 * Register the given directory with the WatchService
 */
private void register(Path dir) throws IOException {
    System.out.format( " Interrupt Status:  " +Thread.currentThread().isInterrupted()+ "  at line a
 " , dir);//line a
    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
     System.out.format( " Interrupt Status:  " +Thread.currentThread().isInterrupted()+ "  at line b. 
 " , dir);//line b
    Path prev = keys.get(key);

    keys.put(key, dir);
}

/**
 * Register the given directory, and all its sub-directories, with the
 * WatchService.
 */
private void registerAll(final Path start) throws IOException {
    // register directory and sub-directories
    Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
            throws IOException        {
            //System.out.format(Thread.currentThread().isInterrupted()+ "  register: %s
 " , dir);
            register(dir);
            return FileVisitResult.CONTINUE;
        }
    });

}

/**
 * Creates a WatchService and registers the given directory
 */
WatchDir(Path dir, boolean recursive) throws IOException {
    this.watcher = FileSystems.getDefault().newWatchService();
    this.keys = new HashMap<WatchKey,Path>();
    this.recursive = recursive;
    //System.out.format( " Scanning %s ... " +Thread.currentThread().isInterrupted()+ " 
  " , dir);
        registerAll(dir);
}

/**
 * Process all events for keys queued to the watcher
 */



public static void main(String[] args) throws Exception {

    // register directory and process its events
    File f = new File( " test_interrupt_status " );
f.mkdir();
Path dir = new File( " ./test_interrupt_status " ).toPath();
    final Thread g = Thread.currentThread();
    new Thread(new Runnable(){

        public void run() {
            try {
                Thread.currentThread().sleep(10);//keep changing values here. line c
                System.out.println( " interrupted " );
                g.interrupt();
            } catch (InterruptedException ex) {
                Logger.getLogger(WatchDir.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

    }).start();
    //System.out.println(Thread.currentThread().isInterrupted());
    new WatchDir(dir, true);
}
}
---------- END SOURCE ----------

SUPPORT :
YES
Comments
Yes, thanks Alan! I tracked down the reproducer code, and this is exactly the place where the interrupted status was cleared.
06-05-2014

Each of the WatchService implementations has a background thread to service register requests and deal with notifications. It's an oversight in AbstractPoller.awaitRequest that it doesn't reset the interrupt status. This might be a starting point: diff --git a/src/share/classes/sun/nio/fs/AbstractPoller.java b/src/share/classes/sun/nio/fs/AbstractPoller.java --- a/src/share/classes/sun/nio/fs/AbstractPoller.java +++ b/src/share/classes/sun/nio/fs/AbstractPoller.java @@ -192,14 +192,17 @@ * the request. */ Object awaitResult() { + boolean interrupted = false; synchronized (this) { while (!completed) { try { wait(); } catch (InterruptedException x) { - // ignore + interrupted = true; } } + if (interrupted) + Thread.currentThread().interrupt(); return result; } }
06-05-2014

minimal test: import java.nio.file.Path; import java.nio.file.Files; import java.nio.file.FileSystems; import static java.nio.file.StandardWatchEventKinds.*; public class WatchDir { public static void main(String[] args) throws Exception { final Path path = Files.createTempDirectory(null); Thread.currentThread().interrupt(); System.out.println("Interrupt Status: " + Thread.currentThread().isInterrupted() + " BEFORE registration"); path.register( FileSystems.getDefault().newWatchService(), ENTRY_CREATE); System.out.println("Interrupt Status: " + Thread.currentThread().isInterrupted() + " AFTER registration"); } }
06-05-2014