In a server, I am initializing the working directory for a resource on first access to the resource. There may be parallel requests for the resource handled by multiple processes of the server, which means that I need to take care that none of the processes sees a partially initialized working directory. The solution for this is initialize the working directories in a temporary, sibling directory and then move it to its final location with Files.move
and the StandardCopyOption.ATOMIC_MOVE
.
In case two processes initialize a working directory at the same time, the second atomic move fails. This is not really a problem because then the working directory is initialized correctly, so the process that came in second only needs to discard the temporary directory it created and continue.
I tried to do this with the following code:
private void initalizeWorkDirectory(final Resource resource) throws IOException {
File workDir = resource.getWorkDirectory();
if (!workDir.exists()) {
File tempDir = createTemporarySibligDirectory(workDir);
try {
fillWorkDirectory(tempDir, resource);
Files.move(tempDir.toPath(), workDir.toPath(), StandardCopyOption.ATOMIC_MOVE);
} catch (FileAlreadyExistsException e) {
// do some logging
} finally {
FileUtils.deleteQuietly(tempDir);
}
}
}
However I noticed, that only catching the FileAlreadyExistsException
doesn't seem enough. In case of a move collision, there are also other exceptions thrown. I don't just want to catch all exceptions because this could be hiding real problems.
So isn't there a way to reliably detect from the exception thrown by Files.move that an atomic move of a directory failed because the target directory already existed?
From observing the exceptions and by looking at the
FileSystemProvider
implementations for Windows and Linux, we found out the following:AccessDeniedException
is thrown when trying to move a directory to an existing directory.errno.h
constantENOTEMPTY
.When programming against these implementation details, one can reasonably well detect directory move collisions without too much danger of hiding other problems.
The following code implements an atomic move which always throws a
FileAlreadyExistsException
in case of a move collision: