Added tests and documentation for msync(2) behavior.

geesefs-0-30-9
Aaron Jacobs 2015-03-24 13:50:10 +11:00
commit 9037c45993
2 changed files with 196 additions and 13 deletions

View File

@ -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

View File

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