8029516: (fs) WatchKey cancel unreliable on Windows
Reviewed-by: chegar
This commit is contained in:
parent
7eb2dc516c
commit
2689839d79
@ -36,6 +36,17 @@ import sun.misc.Unsafe;
|
|||||||
class WindowsNativeDispatcher {
|
class WindowsNativeDispatcher {
|
||||||
private WindowsNativeDispatcher() { }
|
private WindowsNativeDispatcher() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HANDLE CreateEvent(
|
||||||
|
* LPSECURITY_ATTRIBUTES lpEventAttributes,
|
||||||
|
* BOOL bManualReset,
|
||||||
|
* BOOL bInitialState,
|
||||||
|
* PCTSTR lpName
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
static native long CreateEvent(boolean bManualReset, boolean bInitialState)
|
||||||
|
throws WindowsException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HANDLE CreateFile(
|
* HANDLE CreateFile(
|
||||||
* LPCTSTR lpFileName,
|
* LPCTSTR lpFileName,
|
||||||
@ -1041,6 +1052,25 @@ class WindowsNativeDispatcher {
|
|||||||
long pOverlapped)
|
long pOverlapped)
|
||||||
throws WindowsException;
|
throws WindowsException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CancelIo(
|
||||||
|
* HANDLE hFile
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
static native void CancelIo(long hFile) throws WindowsException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetOverlappedResult(
|
||||||
|
* HANDLE hFile,
|
||||||
|
* LPOVERLAPPED lpOverlapped,
|
||||||
|
* LPDWORD lpNumberOfBytesTransferred,
|
||||||
|
* BOOL bWait
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
static native int GetOverlappedResult(long hFile, long lpOverlapped)
|
||||||
|
throws WindowsException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BackupRead(
|
* BackupRead(
|
||||||
* HANDLE hFile,
|
* HANDLE hFile,
|
||||||
|
@ -25,9 +25,16 @@
|
|||||||
|
|
||||||
package sun.nio.fs;
|
package sun.nio.fs;
|
||||||
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardWatchEventKinds;
|
||||||
|
import java.nio.file.WatchEvent;
|
||||||
|
import java.nio.file.WatchKey;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.sun.nio.file.ExtendedWatchEventModifier;
|
import com.sun.nio.file.ExtendedWatchEventModifier;
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
@ -42,7 +49,6 @@ class WindowsWatchService
|
|||||||
extends AbstractWatchService
|
extends AbstractWatchService
|
||||||
{
|
{
|
||||||
private final static int WAKEUP_COMPLETION_KEY = 0;
|
private final static int WAKEUP_COMPLETION_KEY = 0;
|
||||||
private final Unsafe unsafe = Unsafe.getUnsafe();
|
|
||||||
|
|
||||||
// background thread to service I/O completion port
|
// background thread to service I/O completion port
|
||||||
private final Poller poller;
|
private final Poller poller;
|
||||||
@ -82,7 +88,7 @@ class WindowsWatchService
|
|||||||
/**
|
/**
|
||||||
* Windows implementation of WatchKey.
|
* Windows implementation of WatchKey.
|
||||||
*/
|
*/
|
||||||
private class WindowsWatchKey extends AbstractWatchKey {
|
private static class WindowsWatchKey extends AbstractWatchKey {
|
||||||
// file key (used to detect existing registrations)
|
// file key (used to detect existing registrations)
|
||||||
private final FileKey fileKey;
|
private final FileKey fileKey;
|
||||||
|
|
||||||
@ -169,15 +175,9 @@ class WindowsWatchService
|
|||||||
return completionKey;
|
return completionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// close directory and release buffer
|
// Invalidate the key, assumes that resources have been released
|
||||||
void releaseResources() {
|
|
||||||
CloseHandle(handle);
|
|
||||||
buffer.cleaner().clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidate key by closing directory and releasing buffer
|
|
||||||
void invalidate() {
|
void invalidate() {
|
||||||
releaseResources();
|
((WindowsWatchService)watcher()).poller.releaseResources(this);
|
||||||
handle = INVALID_HANDLE_VALUE;
|
handle = INVALID_HANDLE_VALUE;
|
||||||
buffer = null;
|
buffer = null;
|
||||||
countAddress = 0;
|
countAddress = 0;
|
||||||
@ -193,7 +193,7 @@ class WindowsWatchService
|
|||||||
public void cancel() {
|
public void cancel() {
|
||||||
if (isValid()) {
|
if (isValid()) {
|
||||||
// delegate to poller
|
// delegate to poller
|
||||||
poller.cancel(this);
|
((WindowsWatchService)watcher()).poller.cancel(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,18 +241,25 @@ class WindowsWatchService
|
|||||||
/**
|
/**
|
||||||
* Background thread to service I/O completion port.
|
* Background thread to service I/O completion port.
|
||||||
*/
|
*/
|
||||||
private class Poller extends AbstractPoller {
|
private static class Poller extends AbstractPoller {
|
||||||
|
private final static Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* typedef struct _OVERLAPPED {
|
* typedef struct _OVERLAPPED {
|
||||||
* DWORD Internal;
|
* ULONG_PTR Internal;
|
||||||
* DWORD InternalHigh;
|
* ULONG_PTR InternalHigh;
|
||||||
* DWORD Offset;
|
* union {
|
||||||
* DWORD OffsetHigh;
|
* struct { DWORD Offset; DWORD OffsetHigh; };
|
||||||
* HANDLE hEvent;
|
* PVOID Pointer;
|
||||||
|
* };
|
||||||
|
* HANDLE hEvent;
|
||||||
* } OVERLAPPED;
|
* } OVERLAPPED;
|
||||||
*/
|
*/
|
||||||
private static final short SIZEOF_DWORD = 4;
|
private static final short SIZEOF_DWORD = 4;
|
||||||
private static final short SIZEOF_OVERLAPPED = 32; // 20 on 32-bit
|
private static final short SIZEOF_OVERLAPPED = 32; // 20 on 32-bit
|
||||||
|
private static final short OFFSETOF_HEVENT =
|
||||||
|
(UNSAFE.addressSize() == 4) ? (short) 16 : 24;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* typedef struct _FILE_NOTIFY_INFORMATION {
|
* typedef struct _FILE_NOTIFY_INFORMATION {
|
||||||
@ -276,10 +283,10 @@ class WindowsWatchService
|
|||||||
private final long port;
|
private final long port;
|
||||||
|
|
||||||
// maps completion key to WatchKey
|
// maps completion key to WatchKey
|
||||||
private final Map<Integer,WindowsWatchKey> ck2key;
|
private final Map<Integer, WindowsWatchKey> ck2key;
|
||||||
|
|
||||||
// maps file key to WatchKey
|
// maps file key to WatchKey
|
||||||
private final Map<FileKey,WindowsWatchKey> fk2key;
|
private final Map<FileKey, WindowsWatchKey> fk2key;
|
||||||
|
|
||||||
// unique completion key for each directory
|
// unique completion key for each directory
|
||||||
// native completion key capacity is 64 bits on Win64.
|
// native completion key capacity is 64 bits on Win64.
|
||||||
@ -393,8 +400,13 @@ class WindowsWatchService
|
|||||||
long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED;
|
long overlappedAddress = bufferAddress + size - SIZEOF_OVERLAPPED;
|
||||||
long countAddress = overlappedAddress - SIZEOF_DWORD;
|
long countAddress = overlappedAddress - SIZEOF_DWORD;
|
||||||
|
|
||||||
|
// zero the overlapped structure
|
||||||
|
UNSAFE.setMemory(overlappedAddress, SIZEOF_OVERLAPPED, (byte)0);
|
||||||
|
|
||||||
// start async read of changes to directory
|
// start async read of changes to directory
|
||||||
try {
|
try {
|
||||||
|
createAndAttachEvent(overlappedAddress);
|
||||||
|
|
||||||
ReadDirectoryChangesW(handle,
|
ReadDirectoryChangesW(handle,
|
||||||
bufferAddress,
|
bufferAddress,
|
||||||
CHANGES_BUFFER_SIZE,
|
CHANGES_BUFFER_SIZE,
|
||||||
@ -403,6 +415,7 @@ class WindowsWatchService
|
|||||||
countAddress,
|
countAddress,
|
||||||
overlappedAddress);
|
overlappedAddress);
|
||||||
} catch (WindowsException x) {
|
} catch (WindowsException x) {
|
||||||
|
closeAttachedEvent(overlappedAddress);
|
||||||
buffer.release();
|
buffer.release();
|
||||||
return new IOException(x.getMessage());
|
return new IOException(x.getMessage());
|
||||||
}
|
}
|
||||||
@ -421,7 +434,7 @@ class WindowsWatchService
|
|||||||
// 2. release existing key's resources (handle/buffer)
|
// 2. release existing key's resources (handle/buffer)
|
||||||
// 3. re-initialize key with new handle/buffer
|
// 3. re-initialize key with new handle/buffer
|
||||||
ck2key.remove(existing.completionKey());
|
ck2key.remove(existing.completionKey());
|
||||||
existing.releaseResources();
|
releaseResources(existing);
|
||||||
watchKey = existing.init(handle, events, watchSubtree, buffer,
|
watchKey = existing.init(handle, events, watchSubtree, buffer,
|
||||||
countAddress, overlappedAddress, completionKey);
|
countAddress, overlappedAddress, completionKey);
|
||||||
}
|
}
|
||||||
@ -436,6 +449,42 @@ class WindowsWatchService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the outstanding I/O operation on the directory
|
||||||
|
* associated with the given key and releases the associated
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
CloseHandle(key.handle());
|
||||||
|
closeAttachedEvent(key.overlappedAddress());
|
||||||
|
key.buffer().cleaner().clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an unnamed event and set it as the hEvent field
|
||||||
|
* in the given OVERLAPPED structure
|
||||||
|
*/
|
||||||
|
private void createAndAttachEvent(long ov) throws WindowsException {
|
||||||
|
long hEvent = CreateEvent(false, false);
|
||||||
|
UNSAFE.putAddress(ov + OFFSETOF_HEVENT, hEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the event attached to the given OVERLAPPED structure. A
|
||||||
|
* no-op if there isn't an event attached.
|
||||||
|
*/
|
||||||
|
private void closeAttachedEvent(long ov) {
|
||||||
|
long hEvent = UNSAFE.getAddress(ov + OFFSETOF_HEVENT);
|
||||||
|
if (hEvent != 0 && hEvent != INVALID_HANDLE_VALUE)
|
||||||
|
CloseHandle(hEvent);
|
||||||
|
}
|
||||||
|
|
||||||
// cancel single key
|
// cancel single key
|
||||||
@Override
|
@Override
|
||||||
void implCancelKey(WatchKey obj) {
|
void implCancelKey(WatchKey obj) {
|
||||||
@ -451,9 +500,8 @@ class WindowsWatchService
|
|||||||
@Override
|
@Override
|
||||||
void implCloseAll() {
|
void implCloseAll() {
|
||||||
// cancel all keys
|
// cancel all keys
|
||||||
for (Map.Entry<Integer, WindowsWatchKey> entry: ck2key.entrySet()) {
|
ck2key.values().forEach(WindowsWatchKey::invalidate);
|
||||||
entry.getValue().invalidate();
|
|
||||||
}
|
|
||||||
fk2key.clear();
|
fk2key.clear();
|
||||||
ck2key.clear();
|
ck2key.clear();
|
||||||
|
|
||||||
@ -462,8 +510,7 @@ class WindowsWatchService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate file change action into watch event
|
// Translate file change action into watch event
|
||||||
private WatchEvent.Kind<?> translateActionToEvent(int action)
|
private WatchEvent.Kind<?> translateActionToEvent(int action) {
|
||||||
{
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case FILE_ACTION_MODIFIED :
|
case FILE_ACTION_MODIFIED :
|
||||||
return StandardWatchEventKinds.ENTRY_MODIFY;
|
return StandardWatchEventKinds.ENTRY_MODIFY;
|
||||||
@ -487,18 +534,18 @@ class WindowsWatchService
|
|||||||
|
|
||||||
int nextOffset;
|
int nextOffset;
|
||||||
do {
|
do {
|
||||||
int action = unsafe.getInt(address + OFFSETOF_ACTION);
|
int action = UNSAFE.getInt(address + OFFSETOF_ACTION);
|
||||||
|
|
||||||
// map action to event
|
// map action to event
|
||||||
WatchEvent.Kind<?> kind = translateActionToEvent(action);
|
WatchEvent.Kind<?> kind = translateActionToEvent(action);
|
||||||
if (key.events().contains(kind)) {
|
if (key.events().contains(kind)) {
|
||||||
// copy the name
|
// copy the name
|
||||||
int nameLengthInBytes = unsafe.getInt(address + OFFSETOF_FILENAMELENGTH);
|
int nameLengthInBytes = UNSAFE.getInt(address + OFFSETOF_FILENAMELENGTH);
|
||||||
if ((nameLengthInBytes % 2) != 0) {
|
if ((nameLengthInBytes % 2) != 0) {
|
||||||
throw new AssertionError("FileNameLength.FileNameLength is not a multiple of 2");
|
throw new AssertionError("FileNameLength is not a multiple of 2");
|
||||||
}
|
}
|
||||||
char[] nameAsArray = new char[nameLengthInBytes/2];
|
char[] nameAsArray = new char[nameLengthInBytes/2];
|
||||||
unsafe.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray,
|
UNSAFE.copyMemory(null, address + OFFSETOF_FILENAME, nameAsArray,
|
||||||
Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
|
Unsafe.ARRAY_CHAR_BASE_OFFSET, nameLengthInBytes);
|
||||||
|
|
||||||
// create FileName and queue event
|
// create FileName and queue event
|
||||||
@ -508,7 +555,7 @@ class WindowsWatchService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// next event
|
// next event
|
||||||
nextOffset = unsafe.getInt(address + OFFSETOF_NEXTENTRYOFFSET);
|
nextOffset = UNSAFE.getInt(address + OFFSETOF_NEXTENTRYOFFSET);
|
||||||
address += (long)nextOffset;
|
address += (long)nextOffset;
|
||||||
} while (nextOffset != 0);
|
} while (nextOffset != 0);
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,17 @@ Java_sun_nio_fs_WindowsNativeDispatcher_initIDs(JNIEnv* env, jclass this)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_sun_nio_fs_WindowsNativeDispatcher_CreateEvent(JNIEnv* env, jclass this,
|
||||||
|
jboolean bManualReset, jboolean bInitialState)
|
||||||
|
{
|
||||||
|
HANDLE hEvent = CreateEventW(NULL, bManualReset, bInitialState, NULL);
|
||||||
|
if (hEvent == NULL) {
|
||||||
|
throwWindowsException(env, GetLastError());
|
||||||
|
}
|
||||||
|
return ptr_to_jlong(hEvent);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_sun_nio_fs_WindowsNativeDispatcher_FormatMessage(JNIEnv* env, jclass this, jint errorCode) {
|
Java_sun_nio_fs_WindowsNativeDispatcher_FormatMessage(JNIEnv* env, jclass this, jint errorCode) {
|
||||||
WCHAR message[255];
|
WCHAR message[255];
|
||||||
@ -1229,6 +1240,31 @@ Java_sun_nio_fs_WindowsNativeDispatcher_PostQueuedCompletionStatus(JNIEnv* env,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_sun_nio_fs_WindowsNativeDispatcher_CancelIo(JNIEnv* env, jclass this, jlong hFile) {
|
||||||
|
if (CancelIo((HANDLE)jlong_to_ptr(hFile)) == 0) {
|
||||||
|
throwWindowsException(env, GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_sun_nio_fs_WindowsNativeDispatcher_GetOverlappedResult(JNIEnv *env, jclass this,
|
||||||
|
jlong hFile, jlong lpOverlapped)
|
||||||
|
{
|
||||||
|
BOOL res;
|
||||||
|
DWORD bytesTransferred = -1;
|
||||||
|
|
||||||
|
res = GetOverlappedResult((HANDLE)jlong_to_ptr(hFile),
|
||||||
|
(LPOVERLAPPED)jlong_to_ptr(lpOverlapped),
|
||||||
|
&bytesTransferred,
|
||||||
|
TRUE);
|
||||||
|
if (res == 0) {
|
||||||
|
throwWindowsException(env, GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (jint)bytesTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_sun_nio_fs_WindowsNativeDispatcher_ReadDirectoryChangesW(JNIEnv* env, jclass this,
|
Java_sun_nio_fs_WindowsNativeDispatcher_ReadDirectoryChangesW(JNIEnv* env, jclass this,
|
||||||
jlong hDirectory, jlong bufferAddress, jint bufferLength, jboolean watchSubTree, jint filter,
|
jlong hDirectory, jlong bufferAddress, jint bufferLength, jboolean watchSubTree, jint filter,
|
||||||
@ -1236,17 +1272,7 @@ Java_sun_nio_fs_WindowsNativeDispatcher_ReadDirectoryChangesW(JNIEnv* env, jclas
|
|||||||
{
|
{
|
||||||
BOOL res;
|
BOOL res;
|
||||||
BOOL subtree = (watchSubTree == JNI_TRUE) ? TRUE : FALSE;
|
BOOL subtree = (watchSubTree == JNI_TRUE) ? TRUE : FALSE;
|
||||||
|
LPOVERLAPPED ov = (LPOVERLAPPED)jlong_to_ptr(pOverlapped);
|
||||||
/* Any unused members of [OVERLAPPED] structure should always be initialized to zero
|
|
||||||
before the structure is used in a function call.
|
|
||||||
Otherwise, the function may fail and return ERROR_INVALID_PARAMETER.
|
|
||||||
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684342%28v=vs.85%29.aspx
|
|
||||||
|
|
||||||
The [Offset] and [OffsetHigh] members of this structure are not used.
|
|
||||||
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
|
|
||||||
|
|
||||||
[hEvent] should be zero, other fields are the return values. */
|
|
||||||
ZeroMemory((LPOVERLAPPED)jlong_to_ptr(pOverlapped), sizeof(OVERLAPPED));
|
|
||||||
|
|
||||||
res = ReadDirectoryChangesW((HANDLE)jlong_to_ptr(hDirectory),
|
res = ReadDirectoryChangesW((HANDLE)jlong_to_ptr(hDirectory),
|
||||||
(LPVOID)jlong_to_ptr(bufferAddress),
|
(LPVOID)jlong_to_ptr(bufferAddress),
|
||||||
|
119
jdk/test/java/nio/file/WatchService/LotsOfCancels.java
Normal file
119
jdk/test/java/nio/file/WatchService/LotsOfCancels.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @test
|
||||||
|
* @bug 8029516
|
||||||
|
* @summary Bash on WatchKey.cancel with a view to causing a crash when
|
||||||
|
* an outstanding I/O operation on directory completes after the
|
||||||
|
* directory has been closed
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.nio.file.ClosedWatchServiceException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.WatchKey;
|
||||||
|
import java.nio.file.WatchService;
|
||||||
|
import static java.nio.file.StandardWatchEventKinds.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class LotsOfCancels {
|
||||||
|
|
||||||
|
// set to true for any exceptions
|
||||||
|
static volatile boolean failed;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
// create a bunch of directories. Create two tasks for each directory,
|
||||||
|
// one to bash on cancel, the other to poll the events
|
||||||
|
ExecutorService pool = Executors.newCachedThreadPool();
|
||||||
|
try {
|
||||||
|
Path top = Files.createTempDirectory("work");
|
||||||
|
top.toFile().deleteOnExit();
|
||||||
|
for (int i=1; i<=16; i++) {
|
||||||
|
Path dir = Files.createDirectory(top.resolve("dir-" + i));
|
||||||
|
WatchService watcher = FileSystems.getDefault().newWatchService();
|
||||||
|
pool.submit(() -> handle(dir, watcher));
|
||||||
|
pool.submit(() -> poll(watcher));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
pool.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// give thread pool lots of time to terminate
|
||||||
|
if (!pool.awaitTermination(5L, TimeUnit.MINUTES))
|
||||||
|
throw new RuntimeException("Thread pool did not terminate");
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
throw new RuntimeException("Test failed, see log for details");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stress the given WatchService, specifically the cancel method, in
|
||||||
|
* the given directory. Closes the WatchService when done.
|
||||||
|
*/
|
||||||
|
static void handle(Path dir, WatchService watcher) {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
Path file = dir.resolve("anyfile");
|
||||||
|
for (int i=0; i<2000; i++) {
|
||||||
|
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE);
|
||||||
|
Files.createFile(file);
|
||||||
|
Files.delete(file);
|
||||||
|
key.cancel();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
watcher.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls the given WatchService in a tight loop. This keeps the event
|
||||||
|
* queue drained, it also hogs a CPU core which seems necessary to
|
||||||
|
* tickle the original bug.
|
||||||
|
*/
|
||||||
|
static void poll(WatchService watcher) {
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
WatchKey key = watcher.take();
|
||||||
|
if (key != null) {
|
||||||
|
key.pollEvents();
|
||||||
|
key.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ClosedWatchServiceException expected) {
|
||||||
|
// nothing to do
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user