Added tests and documentation for msync(2) behavior.
commit
9037c45993
|
@ -218,9 +218,12 @@ type FileSystem interface {
|
||||||
//
|
//
|
||||||
// * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
|
// * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
|
||||||
// vfs_fsync_range.
|
// vfs_fsync_range.
|
||||||
|
//
|
||||||
// * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
|
// * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
|
||||||
//
|
//
|
||||||
// Note that this is also called by fdatasync(2) (cf. http://goo.gl/01R7rF).
|
// Note that this is also called by fdatasync(2) (cf. http://goo.gl/01R7rF),
|
||||||
|
// and may be called for msync(2) with the MS_SYNC flag (see the notes on
|
||||||
|
// FlushFile).
|
||||||
//
|
//
|
||||||
// See also: FlushFile, which may perform a similar purpose when closing a
|
// See also: FlushFile, which may perform a similar purpose when closing a
|
||||||
// file (but which is not used in "real" file systems).
|
// file (but which is not used in "real" file systems).
|
||||||
|
@ -242,11 +245,28 @@ type FileSystem interface {
|
||||||
// case of close(2), a flush error is returned to the user. For dup2(2), it
|
// case of close(2), a flush error is returned to the user. For dup2(2), it
|
||||||
// is not.
|
// is not.
|
||||||
//
|
//
|
||||||
// Note that one potentially significant case where this is *not* called is
|
// One potentially significant case where this may not be called is mmap'd
|
||||||
// munmap(2). (Cf. http://goo.gl/7n1r9X, fuse-devel mailing list thread from
|
// files, where the behavior is complicated:
|
||||||
// Han-Wen Nienhuys on 2014-10-08.) Even if users close(2) after writing to
|
//
|
||||||
// an mmap'd file, on OS X the contents are not immediately flushed (cf.
|
// * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
|
||||||
// https://github.com/osxfuse/osxfuse/issues/202).
|
//
|
||||||
|
// * On OS X, if a user modifies a mapped file via the mapping before
|
||||||
|
// closing the file with close(2), the WriteFile calls for the
|
||||||
|
// modifications may not be received before the FlushFile request for the
|
||||||
|
// close(2) (cf. http://goo.gl/kVmNcx).
|
||||||
|
//
|
||||||
|
// * However, even on OS X you can arrange for writes via a mapping to be
|
||||||
|
// flushed by calling msync(2) followed by close(2). On OS X msync(2)
|
||||||
|
// will cause a WriteFile to go through and close(2) will cause a
|
||||||
|
// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
|
||||||
|
// nothing unless you set the MS_SYNC flag, in which case it causes a
|
||||||
|
// SyncFile (cf. http://goo.gl/P3mErk).
|
||||||
|
//
|
||||||
|
// In summary: if you make data durable in both FlushFile and SyncFile, then
|
||||||
|
// your users can get safe behavior from mapped files by calling msync(2)
|
||||||
|
// with MS_SYNC, followed by munmap(2), followed by close(2). On Linux, the
|
||||||
|
// msync(2) appears to be optional because close(2) implies dirty page
|
||||||
|
// writeback (cf. http://goo.gl/HyzLTT).
|
||||||
//
|
//
|
||||||
// Because of cases like dup2(2), calls to FlushFile are not necessarily one
|
// Because of cases like dup2(2), calls to FlushFile are not necessarily one
|
||||||
// to one with calls to OpenFile. They should not be used for reference
|
// to one with calls to OpenFile. They should not be used for reference
|
||||||
|
|
|
@ -24,6 +24,9 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
"github.com/jacobsa/bazilfuse"
|
||||||
"github.com/jacobsa/fuse/fsutil"
|
"github.com/jacobsa/fuse/fsutil"
|
||||||
|
@ -107,6 +110,8 @@ func (t *flushFSTest) TearDown() {
|
||||||
// Helpers
|
// Helpers
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var isDarwin = runtime.GOOS == "darwin"
|
||||||
|
|
||||||
func readReports(f *os.File) (reports []string, err error) {
|
func readReports(f *os.File) (reports []string, err error) {
|
||||||
// Seek the file to the start.
|
// Seek the file to the start.
|
||||||
_, err = f.Seek(0, 0)
|
_, err = f.Seek(0, 0)
|
||||||
|
@ -173,6 +178,23 @@ func dup2(oldfd int, newfd int) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call msync(2) with the MS_SYNC flag on a slice previously returned by
|
||||||
|
// mmap(2).
|
||||||
|
func msync(p []byte) (err error) {
|
||||||
|
_, _, errno := unix.Syscall(
|
||||||
|
unix.SYS_MSYNC,
|
||||||
|
uintptr(unsafe.Pointer(&p[0])),
|
||||||
|
uintptr(len(p)),
|
||||||
|
unix.MS_SYNC)
|
||||||
|
|
||||||
|
if errno != 0 {
|
||||||
|
err = errno
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// No errors
|
// No errors
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -456,7 +478,6 @@ func (t *NoErrorsTest) Dup() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
isDarwin := runtime.GOOS == "darwin"
|
|
||||||
var expectedFlushes []interface{}
|
var expectedFlushes []interface{}
|
||||||
|
|
||||||
// Open the file.
|
// Open the file.
|
||||||
|
@ -542,7 +563,7 @@ func (t *NoErrorsTest) Dup2() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
func (t *NoErrorsTest) Mmap_NoMsync_MunmapBeforeClose() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -564,9 +585,10 @@ func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
AssertEq("taco", string(data))
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
// Modify then unmap.
|
// Modify the contents.
|
||||||
data[0] = 'p'
|
data[0] = 'p'
|
||||||
|
|
||||||
|
// Unmap.
|
||||||
err = syscall.Munmap(data)
|
err = syscall.Munmap(data)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
@ -580,7 +602,7 @@ func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||||
t.f1 = nil
|
t.f1 = nil
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if isDarwin {
|
||||||
ExpectThat(t.getFlushes(), ElementsAre("taco"))
|
ExpectThat(t.getFlushes(), ElementsAre("taco"))
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
} else {
|
} else {
|
||||||
|
@ -589,7 +611,7 @@ func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
func (t *NoErrorsTest) Mmap_NoMsync_CloseBeforeMunmap() {
|
||||||
var n int
|
var n int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -619,9 +641,10 @@ func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
||||||
AssertThat(t.getFlushes(), ElementsAre("taco"))
|
AssertThat(t.getFlushes(), ElementsAre("taco"))
|
||||||
AssertThat(t.getFsyncs(), ElementsAre())
|
AssertThat(t.getFsyncs(), ElementsAre())
|
||||||
|
|
||||||
// Modify then unmap.
|
// Modify the contents.
|
||||||
data[0] = 'p'
|
data[0] = 'p'
|
||||||
|
|
||||||
|
// Unmap.
|
||||||
err = syscall.Munmap(data)
|
err = syscall.Munmap(data)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
@ -630,6 +653,117 @@ func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
||||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *NoErrorsTest) Mmap_WithMsync_MunmapBeforeClose() {
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var expectedFsyncs []interface{}
|
||||||
|
|
||||||
|
// Open the file.
|
||||||
|
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write some contents to the file.
|
||||||
|
n, err = t.f1.Write([]byte("taco"))
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// mmap the file.
|
||||||
|
data, err := syscall.Mmap(
|
||||||
|
int(t.f1.Fd()), 0, 4,
|
||||||
|
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||||
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
|
// Modify the contents.
|
||||||
|
data[0] = 'p'
|
||||||
|
|
||||||
|
// msync. This causes an fsync, except on OS X (cf.
|
||||||
|
// https://github.com/osxfuse/osxfuse/issues/202).
|
||||||
|
err = msync(data)
|
||||||
|
ExpectEq(nil, err)
|
||||||
|
|
||||||
|
if !isDarwin {
|
||||||
|
expectedFsyncs = append(expectedFsyncs, "paco")
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpectThat(t.getFlushes(), ElementsAre())
|
||||||
|
ExpectThat(t.getFsyncs(), ElementsAre(expectedFsyncs...))
|
||||||
|
|
||||||
|
// Unmap. This does not cause anything.
|
||||||
|
err = syscall.Munmap(data)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
ExpectThat(t.getFlushes(), ElementsAre())
|
||||||
|
ExpectThat(t.getFsyncs(), ElementsAre(expectedFsyncs...))
|
||||||
|
|
||||||
|
// Close the file. We should now see a flush with the modified contents, even
|
||||||
|
// on OS X.
|
||||||
|
err = t.f1.Close()
|
||||||
|
t.f1 = nil
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
ExpectThat(t.getFlushes(), ElementsAre("paco"))
|
||||||
|
ExpectThat(t.getFsyncs(), ElementsAre(expectedFsyncs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NoErrorsTest) Mmap_WithMsync_CloseBeforeMunmap() {
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var expectedFsyncs []interface{}
|
||||||
|
|
||||||
|
// Open the file.
|
||||||
|
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write some contents to the file.
|
||||||
|
n, err = t.f1.Write([]byte("taco"))
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// mmap the file.
|
||||||
|
data, err := syscall.Mmap(
|
||||||
|
int(t.f1.Fd()), 0, 4,
|
||||||
|
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||||
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq("taco", string(data))
|
||||||
|
|
||||||
|
// Close the file. We should see a flush.
|
||||||
|
err = t.f1.Close()
|
||||||
|
t.f1 = nil
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
AssertThat(t.getFlushes(), ElementsAre("taco"))
|
||||||
|
AssertThat(t.getFsyncs(), ElementsAre())
|
||||||
|
|
||||||
|
// Modify the contents.
|
||||||
|
data[0] = 'p'
|
||||||
|
|
||||||
|
// msync. This causes an fsync, except on OS X (cf.
|
||||||
|
// https://github.com/osxfuse/osxfuse/issues/202).
|
||||||
|
err = msync(data)
|
||||||
|
ExpectEq(nil, err)
|
||||||
|
|
||||||
|
if !isDarwin {
|
||||||
|
expectedFsyncs = append(expectedFsyncs, "paco")
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpectThat(t.getFlushes(), ElementsAre("taco"))
|
||||||
|
ExpectThat(t.getFsyncs(), ElementsAre(expectedFsyncs...))
|
||||||
|
|
||||||
|
// Unmap. Again, this does not cause a flush.
|
||||||
|
err = syscall.Munmap(data)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
ExpectThat(t.getFlushes(), ElementsAre("taco"))
|
||||||
|
ExpectThat(t.getFsyncs(), ElementsAre(expectedFsyncs...))
|
||||||
|
}
|
||||||
|
|
||||||
func (t *NoErrorsTest) Directory() {
|
func (t *NoErrorsTest) Directory() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -706,7 +840,7 @@ func (t *FlushErrorTest) Dup() {
|
||||||
err = t.f1.Close()
|
err = t.f1.Close()
|
||||||
t.f1 = nil
|
t.f1 = nil
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if isDarwin {
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
} else {
|
} else {
|
||||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||||
|
@ -780,3 +914,32 @@ func (t *FsyncErrorTest) Fdatasync() {
|
||||||
|
|
||||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *FsyncErrorTest) Msync() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// On OS X, msync does not cause SyncFile.
|
||||||
|
if isDarwin {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file.
|
||||||
|
t.f1, err = os.OpenFile(path.Join(t.Dir, "foo"), os.O_RDWR, 0)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// mmap the file.
|
||||||
|
data, err := syscall.Mmap(
|
||||||
|
int(t.f1.Fd()), 0, 4,
|
||||||
|
syscall.PROT_READ|syscall.PROT_WRITE,
|
||||||
|
syscall.MAP_SHARED)
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// msync the mapping.
|
||||||
|
err = msync(data)
|
||||||
|
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||||
|
|
||||||
|
// Unmap.
|
||||||
|
err = syscall.Munmap(data)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue