8153925: (fs) WatchService hangs on GetOverlappedResult and locks directory (win)
Reviewed-by: alanb
Contributed-by: akashche@redhat.com, tmader@redhat.com
--- a/jdk/src/java.base/windows/classes/sun/nio/fs/WindowsWatchService.java Mon May 02 16:45:38 2016 -0700
+++ b/jdk/src/java.base/windows/classes/sun/nio/fs/WindowsWatchService.java Tue May 03 07:44:52 2016 +0100
@@ -113,6 +113,10 @@
// completion key (used to map I/O completion to WatchKey)
private int completionKey;
+ // flag indicates that ReadDirectoryChangesW failed
+ // and overlapped I/O operation wasn't started
+ private boolean errorStartingOverlapped;
+
WindowsWatchKey(Path dir,
AbstractWatchService watcher,
FileKey fileKey)
@@ -175,6 +179,14 @@
return completionKey;
}
+ void setErrorStartingOverlapped(boolean value) {
+ errorStartingOverlapped = value;
+ }
+
+ boolean isErrorStartingOverlapped() {
+ return errorStartingOverlapped;
+ }
+
// Invalidate the key, assumes that resources have been released
void invalidate() {
((WindowsWatchService)watcher()).poller.releaseResources(this);
@@ -182,6 +194,7 @@
buffer = null;
countAddress = 0;
overlappedAddress = 0;
+ errorStartingOverlapped = false;
}
@Override
@@ -455,11 +468,13 @@
* resources.
*/
private void releaseResources(WindowsWatchKey key) {
- try {
- CancelIo(key.handle());
- GetOverlappedResult(key.handle(), key.overlappedAddress());
- } catch (WindowsException expected) {
- // expected as I/O operation has been cancelled
+ if (!key.isErrorStartingOverlapped()) {
+ try {
+ CancelIo(key.handle());
+ GetOverlappedResult(key.handle(), key.overlappedAddress());
+ } catch (WindowsException expected) {
+ // expected as I/O operation has been cancelled
+ }
}
CloseHandle(key.handle());
closeAttachedEvent(key.overlappedAddress());
@@ -628,6 +643,7 @@
} catch (WindowsException x) {
// no choice but to cancel key
criticalError = true;
+ key.setErrorStartingOverlapped(true);
}
}
if (criticalError) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/nio/file/WatchService/DeleteInterference.java Tue May 03 07:44:52 2016 +0100
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2016, Red Hat, Inc. and/or its affiliates.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @test
+ * @bug 8153925
+ * @summary Tests potential interference between a thread creating and closing
+ * a WatchService with another thread that is deleting and re-creating the
+ * directory at around the same time. This scenario tickled a timing bug
+ * in the Windows implementation.
+ */
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.WatchService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import static java.nio.file.StandardWatchEventKinds.*;
+
+public class DeleteInterference {
+
+ private static final int ITERATIONS_COUNT = 1024;
+
+ /**
+ * Execute two tasks in a thread pool. One task loops on creating and
+ * closing a WatchService, the other task deletes and re-creates the
+ * directory.
+ */
+ public static void main(String[] args) throws Exception {
+ Path dir = Files.createTempDirectory("work");
+ ExecutorService pool = Executors.newCachedThreadPool();
+ try {
+ Future<?> task1 = pool.submit(() -> openAndCloseWatcher(dir));
+ Future<?> task2 = pool.submit(() -> deleteAndRecreateDirectory(dir));
+ task1.get();
+ task2.get();
+ } finally {
+ pool.shutdown();
+ deleteFileTree(dir);
+ }
+ }
+
+ private static void openAndCloseWatcher(Path dir) {
+ FileSystem fs = FileSystems.getDefault();
+ for (int i = 0; i < ITERATIONS_COUNT; i++) {
+ try (WatchService watcher = fs.newWatchService()) {
+ dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ private static void deleteAndRecreateDirectory(Path dir) {
+ for (int i = 0; i < ITERATIONS_COUNT; i++) {
+ try {
+ deleteFileTree(dir);
+ Path subdir = Files.createDirectories(dir.resolve("subdir"));
+ Files.createFile(subdir.resolve("test"));
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ private static void deleteFileTree(Path file) {
+ try {
+ if (Files.isDirectory(file)) {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(file)) {
+ for (Path pa : stream) {
+ deleteFileTree(pa);
+ }
+ }
+ }
+ Files.delete(file);
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+}