forked from lni/vfs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsyncing_file_linux.go
86 lines (73 loc) · 2.74 KB
/
syncing_file_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
// +build linux,!arm
package vfs
import "syscall"
type syncFileRange func(fd int, off int64, n int64, flags int) (err error)
// sync_file_range depends on both the filesystem, and the broader kernel
// support. In particular, Windows Subsystem for Linux does not support
// sync_file_range, even when used with ext{2,3,4}. syncRangeSmokeTest performs
// a test of of sync_file_range, returning false on ENOSYS, and true otherwise.
func syncRangeSmokeTest(fd uintptr, fn syncFileRange) bool {
err := fn(int(fd), 0 /* offset */, 0 /* nbytes */, 0 /* flags */)
return err != syscall.ENOSYS
}
func isSyncRangeSupported(fd uintptr) bool {
var stat syscall.Statfs_t
if err := syscall.Fstatfs(int(fd), &stat); err != nil {
return false
}
// Whitelist which filesystems we allow using sync_file_range with as some
// filesystems treat that syscall as a noop (notably ZFS). A whitelist is
// used instead of a blacklist in order to have a more graceful failure mode
// in case a filesystem we haven't tested is encountered. Currently only
// ext2/3/4 are known to work properly.
const extMagic = 0xef53
switch stat.Type {
case extMagic:
return syncRangeSmokeTest(fd, syscall.SyncFileRange)
}
return false
}
func (f *syncingFile) init() {
if f.fd == 0 {
return
}
f.useSyncRange = isSyncRangeSupported(f.fd)
if f.useSyncRange {
f.syncTo = f.syncToRange
} else {
f.syncTo = f.syncToFdatasync
}
f.syncData = f.syncFdatasync
}
func (f *syncingFile) syncFdatasync() error {
if f.fd == 0 {
return f.File.Sync()
}
return syscall.Fdatasync(int(f.fd))
}
func (f *syncingFile) syncToFdatasync(_ int64) error {
return f.Sync()
}
func (f *syncingFile) syncToRange(offset int64) error {
const (
waitBefore = 0x1
write = 0x2
// waitAfter = 0x4
)
// Note that syncToRange is only called with an offset that is guaranteed to
// be less than atomic.offset (i.e. the write offset). This implies the
// syncingFile.Close will Sync the rest of the data, as well as the file's
// metadata.
f.ratchetSyncOffset(offset)
// By specifying write|waitBefore for the flags, we're instructing
// SyncFileRange to a) wait for any outstanding data being written to finish,
// and b) to queue any other dirty data blocks in the range [0,offset] for
// writing. The actual writing of this data will occur asynchronously. The
// use of `waitBefore` is to limit how much dirty data is allowed to
// accumulate. Linux sometimes behaves poorly when a large amount of dirty
// data accumulates, impacting other I/O operations.
return syscall.SyncFileRange(int(f.fd), 0, offset, write|waitBefore)
}