JDK-8081063 : (fs) WatchService.take() ignores thread interrupt status if a WatchKey is signalled
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 8u45
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2015-05-13
  • Updated: 2018-09-11
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.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) Client VM (build 25.45-b02, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
If a WatchKey is in the signalled state, WatchService.take() returns it even if the thread on which take() is called has its interrupted flag set.

Technically the documentation does not seem to specify the behavior here. The take() methods "throws" Javadoc says "InterruptedException - if interrupted while waiting." It could technically be argued that it was never "waiting" since a key was already signalled.

However this is at odds with how the rest of the Java API works, such as ArrayBlockingQueue, whose take() method prefers to throw an InterruptedException even if an element is available in the queue.

This is also inconsistent with the WatchService.poll(long, TimeUnit) method, which prefers to throw an InterruptedException rather than return the signalled key.

NOTE: In a JDK just prior to this (8u30 or 31 I believe it was called), there was a bug where the WatchKey.reset() method caused a thread's interrupt status to be reset, which could also cause infinite waiting if one is depending on stopping the thread by interrupting it. It appears this was silently fixed at some point; I don't know if it was deliberate or a side effect of another fix. I didn't see a bug in my search regarding it or an entry in the release notes of the two subsequent JDK versions. I mention it here just so you can be aware not to reintroduce it sometime.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached test case. It creates a file to cause a WatchKey to become signalled. Then it resets the key without calling pollEvents(). Though there would be no reason to do this in practice, it simulates what would happen if changes were occurring in the file system faster than the polling loop was processing them. Thus a thread may fail to shut down when expected.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The take() method should throw an InterruptedException if the thread is interrupted, shutting down the polling loop.
ACTUAL -
The take() method returns the signalled WatchKey, causing the loop to run forever.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
	public void test() throws Exception {
		Path temp = Files.createTempDirectory("javatest");
		final WatchService w = FileSystems.getDefault().newWatchService();
		temp.register(w, StandardWatchEventKinds.ENTRY_CREATE);

		Thread th = new Thread("watchThread") {
			@Override
			public void run() {
				try {
					for (;;) {
						System.out.println("before take " + Thread.currentThread().isInterrupted());
						WatchKey k = w.take();
						// There would be no reason in practice to reset the key
						// without polling the events, but this is to simulate what
						// would happen if file changes were happening faster than
						// the loop was handling them.
						//k.pollEvents();
						System.out.println("before reset " + Thread.currentThread().isInterrupted());
						k.reset();
						System.out.println("after reset " + Thread.currentThread().isInterrupted());
					}
				} catch (InterruptedException e) {
					System.out.println("interrupted");
				}
			}
		};

		Files.createFile(temp.resolve("x"));
		th.start();
		th.interrupt();
		th.join();

		System.out.println("done");
	}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Manually check the thread's interrupted status before and/or after calling take().


Comments
Would be nice if the included test case were a complete class not requiring any editing instead of just a code snippet. This latter is obviously however far better than nothing at all.
26-05-2015