1

在服务器中,我在首次访问资源时初始化资源的工作目录。服务器的多个进程处理的资源可能存在并行请求,这意味着我需要注意没有一个进程看到部分初始化的工作目录。解决方案是在一个临时的同级目录中初始化工作目录,然后Files.move使用StandardCopyOption.ATOMIC_MOVE.

如果两个进程同时初始化一个工作目录,则第二个原子移动失败。这不是一个真正的问题,因为工作目录已正确初始化,因此第二个进程只需要丢弃它创建的临时目录并继续。

我尝试使用以下代码执行此操作:

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);
        }
    }
}

但是我注意到,仅仅捕捉FileAlreadyExistsException似乎是不够的。如果发生移动碰撞,还会引发其他异常。我不只是想捕获所有异常,因为这可能隐藏真正的问题。

那么有没有办法从 Files.move 抛出的异常中可靠地检测到由于目标目录已经存在而导致目录的原子移动失败?

4

1 回答 1

1

通过观察异常并查看FileSystemProviderWindows 和 Linux 的实现,我们发现了以下内容:

  • 在 Windows 上,AccessDeniedException尝试将目录移动到现有目录时会抛出 an 。
  • 在 Unix(和它的亲戚)上,一个通用的 FileSystemException 被抛出一个对应于errno.h常量ENOTEMPTY的消息。

当针对这些实现细节进行编程时,可以相当好地检测目录移动冲突,而不会隐藏其他问题。

以下代码实现了一个原子移动,它总是FileAlreadyExistsException在移动碰撞的情况下抛出一个:

import java.io.File;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class AtomicMove {
    private static final String ENOTEMPTY = "Directory not empty";

    public static void move(final File source, final File target) throws FileAlreadyExistsException, IOException {
        try {
            Files.move(source.toPath(), target.toPath(), StandardCopyOption.ATOMIC_MOVE);

        } catch (AccessDeniedException e) {
            // directory move collision on Windows
            throw new FileAlreadyExistsException(source.toString(), target.toString(), e.getMessage());

        } catch (FileSystemException e) {
            if (ENOTEMPTY.equals(e.getReason())) {
                // directory move collision on Unix
                throw new FileAlreadyExistsException(source.toString(), target.toString(), e.getMessage());
            } else {
                // other problem
                throw e;
            }
        }
    }
}
于 2015-06-10T11:33:27.397 回答