Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/128653.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 128653
summary: Add retry for `AccessDeniedException` in `AbstractFileWatchingService`
area: Infra/Settings
type: bug
issues: []
3 changes: 0 additions & 3 deletions muted-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,6 @@ tests:
- class: org.elasticsearch.packaging.test.DockerTests
method: test124CanRestartContainerWithStackLoggingConfig
issue: https://github.com/elastic/elasticsearch/issues/128121
- class: org.elasticsearch.reservedstate.service.FileSettingsServiceIT
method: testSymlinkUpdateTriggerReload
issue: https://github.com/elastic/elasticsearch/issues/128619
- class: org.elasticsearch.packaging.test.DockerTests
method: test085EnvironmentVariablesAreRespectedUnderDockerExec
issue: https://github.com/elastic/elasticsearch/issues/128115
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.AccessDeniedException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
Expand Down Expand Up @@ -56,6 +57,7 @@ public abstract class AbstractFileWatchingService extends AbstractLifecycleCompo

private static final Logger logger = LogManager.getLogger(AbstractFileWatchingService.class);
private static final int REGISTER_RETRY_COUNT = 5;
private static final int ACCESS_DENIED_RETRY_COUNT = 5;
private final Path settingsDir;
private final Map<Path, FileUpdateState> fileUpdateState = new HashMap<>();
private WatchService watchService; // null;
Expand Down Expand Up @@ -115,20 +117,33 @@ public final boolean watching() {
return watcherThread != null;
}

private FileUpdateState readFileUpdateState(Path path) throws IOException {
try {
BasicFileAttributes attr = filesReadAttributes(path, BasicFileAttributes.class);
return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey());
} catch (NoSuchFileException e) {
// file doesn't exist anymore
return null;
}
// package private for testing
FileUpdateState readFileUpdateState(Path path) throws IOException, InterruptedException {
int retryCount = 0;
do {
try {
BasicFileAttributes attr = filesReadAttributes(path, BasicFileAttributes.class);
return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey());
} catch (NoSuchFileException e) {
// file doesn't exist anymore
return null;
} catch (AccessDeniedException e) {
// This can happen on Windows when a symlink is deleted for a path while path.toRealPath() is called. In most cases the
// symlink is recreated, so retry
if (retryCount == ACCESS_DENIED_RETRY_COUNT - 1) {
throw e;
}
logger.debug("Could not read file state [{}] attempt [{}]", path, retryCount);
Thread.sleep(retryDelayMillis(retryCount));
retryCount++;
}
} while (true);
}

// platform independent way to tell if a file changed
// we compare the file modified timestamp, the absolute path (symlinks), and file id on the system
@FixForMultiProject // what do we do when a file is removed?
final boolean fileChanged(Path path) throws IOException {
final boolean fileChanged(Path path) throws IOException, InterruptedException {
FileUpdateState newFileState = readFileUpdateState(path);
if (newFileState == null) {
fileUpdateState.remove(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
Expand Down Expand Up @@ -238,6 +239,23 @@ public void testRegisterWatchKeyRetry() throws IOException, InterruptedException
verify(service, times(2)).retryDelayMillis(anyInt());
}

public void testAccessDeniedRetry() throws IOException, InterruptedException {
var service = spy(fileWatchingService);
doAnswer(i -> 0L).when(service).retryDelayMillis(anyInt());

Files.createDirectories(service.watchedFileDir());
var mockedPath = spy(service.watchedFileDir());

doThrow(new AccessDeniedException("can't read state")).doThrow(new AccessDeniedException("can't read state - attempt 2"))
.doAnswer(i -> mockedPath)
.when(mockedPath)
.toRealPath();

var result = service.readFileUpdateState(mockedPath);
assertNotNull(result);
verify(service, times(2)).retryDelayMillis(anyInt());
}

// helpers
private static void writeTestFile(Path path, String contents) throws IOException {
Path tempFilePath = createTempFile();
Expand Down
Loading