|
17 | 17 |
|
18 | 18 | import java.io.IOException; |
19 | 19 | import java.io.InputStream; |
| 20 | +import java.nio.file.AccessDeniedException; |
20 | 21 | import java.nio.file.ClosedWatchServiceException; |
21 | 22 | import java.nio.file.NoSuchFileException; |
22 | 23 | import java.nio.file.Path; |
@@ -56,6 +57,7 @@ public abstract class AbstractFileWatchingService extends AbstractLifecycleCompo |
56 | 57 |
|
57 | 58 | private static final Logger logger = LogManager.getLogger(AbstractFileWatchingService.class); |
58 | 59 | private static final int REGISTER_RETRY_COUNT = 5; |
| 60 | + private static final int ACCESS_DENIED_RETRY_COUNT = 5; |
59 | 61 | private final Path settingsDir; |
60 | 62 | private final Map<Path, FileUpdateState> fileUpdateState = new HashMap<>(); |
61 | 63 | private WatchService watchService; // null; |
@@ -115,20 +117,33 @@ public final boolean watching() { |
115 | 117 | return watcherThread != null; |
116 | 118 | } |
117 | 119 |
|
118 | | - private FileUpdateState readFileUpdateState(Path path) throws IOException { |
119 | | - try { |
120 | | - BasicFileAttributes attr = filesReadAttributes(path, BasicFileAttributes.class); |
121 | | - return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey()); |
122 | | - } catch (NoSuchFileException e) { |
123 | | - // file doesn't exist anymore |
124 | | - return null; |
125 | | - } |
| 120 | + // package private for testing |
| 121 | + FileUpdateState readFileUpdateState(Path path) throws IOException, InterruptedException { |
| 122 | + int retryCount = 0; |
| 123 | + do { |
| 124 | + try { |
| 125 | + BasicFileAttributes attr = filesReadAttributes(path, BasicFileAttributes.class); |
| 126 | + return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey()); |
| 127 | + } catch (NoSuchFileException e) { |
| 128 | + // file doesn't exist anymore |
| 129 | + return null; |
| 130 | + } catch (AccessDeniedException e) { |
| 131 | + // This can happen on Windows when a symlink is deleted for a path while path.toRealPath() is called. In most cases the |
| 132 | + // symlink is recreated, so retry |
| 133 | + if (retryCount == ACCESS_DENIED_RETRY_COUNT - 1) { |
| 134 | + throw e; |
| 135 | + } |
| 136 | + logger.debug("Could not read file state [{}] attempt [{}]", path, retryCount); |
| 137 | + Thread.sleep(retryDelayMillis(retryCount)); |
| 138 | + retryCount++; |
| 139 | + } |
| 140 | + } while (true); |
126 | 141 | } |
127 | 142 |
|
128 | 143 | // platform independent way to tell if a file changed |
129 | 144 | // we compare the file modified timestamp, the absolute path (symlinks), and file id on the system |
130 | 145 | @FixForMultiProject // what do we do when a file is removed? |
131 | | - final boolean fileChanged(Path path) throws IOException { |
| 146 | + final boolean fileChanged(Path path) throws IOException, InterruptedException { |
132 | 147 | FileUpdateState newFileState = readFileUpdateState(path); |
133 | 148 | if (newFileState == null) { |
134 | 149 | fileUpdateState.remove(path); |
|
0 commit comments