Java fsync Directory

ファイルに対して書き込みをした際に durability を保証するために fsync を呼ぶわけですが、fsync は基本的には(少なくとも ext4 をファイルシステムに使っている場合)親ディレクトリにも fsync を呼んで directory entry の更新も保証してやらないといけません。

で、Java で file に対する fsyncFileChannel.force(Boolean) を呼んでやればいいのですけど、ディレクトリに fsync するのはどうやってやるんだっけ?と思って調べました。

Recent Java 9 commit (e5b66323ae45) breaks fsync on directory

上記スレッドを読んでもらえば、Java の fsync のサポート状況は分かってもらえるかと思います。 結果だけを書きますと、どうも Java の API では明示的にはサポートされていないようです。なので、FileChannel.open でディレクトリを開くという undocumented な dirty hack を使う必要があります。

以下に Scala のサンプルコードを載せておきます。

import java.io.FileOutputStream
import java.nio.channels.FileChannel
import java.nio.file.Paths
import java.nio.file.StandardOpenOption

object Main {
  val filename = "/tmp/foo.txt"

  def main(args: Array[String]): Unit = {
    syncFile()
    syncParent()
  }

  def syncParent(): Unit = {
    val channel = FileChannel.open(Paths.get(filename).getParent, StandardOpenOption.READ)
    channel.force(true)
    channel.close()
  }

  def syncFile(): Unit = {
    val writer = new FileOutputStream(filename)
    writer.getChannel.force(true)
    writer.close()
  }
}

fsync を呼び出せているか確認します。

$ cat Makefile
.PHONY: build run

build:
        sbt stage

run:
        strace -tt -s 1024 -c -f ./target/universal/stage/bin/jvm-fsync-sandbox
$ make build
$ make run
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.72    0.150048         355       422        42 futex
 32.03    0.080478        1133        71        34 wait4
  2.03    0.005112           2      1945           read
  1.36    0.003411           1      1771         2 lseek
  0.95    0.002398           2       939       791 openat
  0.75    0.001889           5       367           mmap
  0.62    0.001564           1      1225       989 stat
  0.51    0.001270           3       397           mprotect
  0.44    0.001094           2       422           rt_sigprocmask
  0.23    0.000577           2       286         6 close
  0.21    0.000520          11        47           munmap
  0.17    0.000415           6        68           sched_getaffinity
  0.15    0.000378           0       436           rt_sigaction
  0.12    0.000295           1       179           fstat
  0.08    0.000204           1       116           lstat
  0.08    0.000200           3        65           clone
  0.06    0.000140           6        23           pipe
  0.05    0.000126           4        30           gettid
  0.05    0.000122           3        36           rt_sigreturn
  0.05    0.000117           2        45           write
  0.04    0.000109           3        30           set_robust_list
  0.04    0.000103           1        57           brk
  0.04    0.000092           1        70        16 access
  0.03    0.000071           1        64           geteuid
  0.03    0.000065           1        56           getegid
  0.02    0.000054           1        32        28 readlink
  0.02    0.000051           0        58           getuid
  0.02    0.000050           0        56           getgid
  0.01    0.000030           2        12           getdents
  0.01    0.000029           1        16         1 fcntl
  0.01    0.000027           1        17           prlimit64
  0.01    0.000026           0        44           getpid
  0.01    0.000025           0        31           dup2
  0.01    0.000021           5         4         4 connect
  0.01    0.000018           3         5           socket
  0.01    0.000017           1        16           arch_prctl
  0.00    0.000012           0        61        45 execve
  0.00    0.000012           1         8           fchdir
  0.00    0.000011           2         5           sysinfo
  0.00    0.000009           1         6           sched_yield
  0.00    0.000009           1         5           uname
  0.00    0.000009           2         4           clock_getres
  0.00    0.000008           2         4           ftruncate
  0.00    0.000007           2         3           chdir
  0.00    0.000005           2         2           set_tid_address
  0.00    0.000004           0         5         4 ioctl
  0.00    0.000004           2         2           kill
  0.00    0.000004           1         3           getcwd
  0.00    0.000004           2         2         2 mkdir
  0.00    0.000002           1         2           getppid
  0.00    0.000002           1         2           getpgrp
  0.00    0.000002           1         2           getgroups
  0.00    0.000001           1         1         1 getsockname
  0.00    0.000000           0         8           madvise
  0.00    0.000000           0         1           socketpair
  0.00    0.000000           0         2           fsync
  0.00    0.000000           0         2           unlink
  0.00    0.000000           0         2         2 fadvise64
  0.00    0.000000           0         1           faccessat
------ ----------- ----------- --------- --------- ----------------
100.00    0.251251                  9591      1967 total

strace の出力結果に fsync が 2 回出てるのでこれで大丈夫でしょう。

Comments

comments powered by Disqus