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
|
||||
// vfs_fsync_range.
|
||||
//
|
||||
// * (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
|
||||
// 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
|
||||
// is not.
|
||||
//
|
||||
// Note that one potentially significant case where this is *not* called is
|
||||
// munmap(2). (Cf. http://goo.gl/7n1r9X, fuse-devel mailing list thread from
|
||||
// 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.
|
||||
// https://github.com/osxfuse/osxfuse/issues/202).
|
||||
// One potentially significant case where this may not be called is mmap'd
|
||||
// files, where the behavior is complicated:
|
||||
//
|
||||
// * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
|
||||
//
|
||||
// * 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
|
||||
// to one with calls to OpenFile. They should not be used for reference
|
||||
|
|
|
@ -24,6 +24,9 @@ import (
|
|||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse/fsutil"
|
||||
|
@ -107,6 +110,8 @@ func (t *flushFSTest) TearDown() {
|
|||
// Helpers
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var isDarwin = runtime.GOOS == "darwin"
|
||||
|
||||
func readReports(f *os.File) (reports []string, err error) {
|
||||
// Seek the file to the start.
|
||||
_, err = f.Seek(0, 0)
|
||||
|
@ -173,6 +178,23 @@ func dup2(oldfd int, newfd int) (err error) {
|
|||
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
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -456,7 +478,6 @@ func (t *NoErrorsTest) Dup() {
|
|||
var n int
|
||||
var err error
|
||||
|
||||
isDarwin := runtime.GOOS == "darwin"
|
||||
var expectedFlushes []interface{}
|
||||
|
||||
// Open the file.
|
||||
|
@ -542,7 +563,7 @@ func (t *NoErrorsTest) Dup2() {
|
|||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
}
|
||||
|
||||
func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
||||
func (t *NoErrorsTest) Mmap_NoMsync_MunmapBeforeClose() {
|
||||
var n int
|
||||
var err error
|
||||
|
||||
|
@ -564,9 +585,10 @@ func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
|||
AssertEq(nil, err)
|
||||
AssertEq("taco", string(data))
|
||||
|
||||
// Modify then unmap.
|
||||
// Modify the contents.
|
||||
data[0] = 'p'
|
||||
|
||||
// Unmap.
|
||||
err = syscall.Munmap(data)
|
||||
AssertEq(nil, err)
|
||||
|
||||
|
@ -580,7 +602,7 @@ func (t *NoErrorsTest) Mmap_MunmapBeforeClose() {
|
|||
t.f1 = nil
|
||||
AssertEq(nil, err)
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
if isDarwin {
|
||||
ExpectThat(t.getFlushes(), ElementsAre("taco"))
|
||||
ExpectThat(t.getFsyncs(), ElementsAre())
|
||||
} 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 err error
|
||||
|
||||
|
@ -619,9 +641,10 @@ func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
|||
AssertThat(t.getFlushes(), ElementsAre("taco"))
|
||||
AssertThat(t.getFsyncs(), ElementsAre())
|
||||
|
||||
// Modify then unmap.
|
||||
// Modify the contents.
|
||||
data[0] = 'p'
|
||||
|
||||
// Unmap.
|
||||
err = syscall.Munmap(data)
|
||||
AssertEq(nil, err)
|
||||
|
||||
|
@ -630,6 +653,117 @@ func (t *NoErrorsTest) Mmap_CloseBeforeMunmap() {
|
|||
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() {
|
||||
var err error
|
||||
|
||||
|
@ -706,7 +840,7 @@ func (t *FlushErrorTest) Dup() {
|
|||
err = t.f1.Close()
|
||||
t.f1 = nil
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
if isDarwin {
|
||||
AssertEq(nil, err)
|
||||
} else {
|
||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
||||
|
@ -780,3 +914,32 @@ func (t *FsyncErrorTest) Fdatasync() {
|
|||
|
||||
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