Upgrade versions of vendored libs
This commit is contained in:
45
vendor/github.com/cockroachdb/pebble/vfs/atomicfs/marker.go
generated
vendored
45
vendor/github.com/cockroachdb/pebble/vfs/atomicfs/marker.go
generated
vendored
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// ReadMarker looks up the current state of a marker returning just the
|
||||
// current value of the marker. Callers that may need to move the marker
|
||||
// should use LocateMarker.
|
||||
// to a new value should use LocateMarker.
|
||||
func ReadMarker(fs vfs.FS, dir, markerName string) (string, error) {
|
||||
state, err := scanForMarker(fs, dir, markerName)
|
||||
if err != nil {
|
||||
@@ -49,9 +49,11 @@ func LocateMarker(fs vfs.FS, dir, markerName string) (*Marker, string, error) {
|
||||
}
|
||||
|
||||
type scannedState struct {
|
||||
// filename is the latest marker file found (the one with the highest iter value).
|
||||
filename string
|
||||
iter uint64
|
||||
value string
|
||||
// obsolete is a list of earlier markers that were found.
|
||||
obsolete []string
|
||||
}
|
||||
|
||||
@@ -89,11 +91,15 @@ func scanForMarker(fs vfs.FS, dir, markerName string) (scannedState, error) {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// A Marker provides an interface for marking a single file on the
|
||||
// filesystem. The marker may be atomically moved from name to name.
|
||||
// Marker is not safe for concurrent use. Multiple processes may not
|
||||
// read or move the same marker simultaneously. An Marker may only be
|
||||
// constructed through LocateMarker.
|
||||
// A Marker provides an interface for maintaining a single string value on the
|
||||
// filesystem. The marker may be atomically moved from value to value.
|
||||
//
|
||||
// The implementation creates a new marker file for each new value, embedding
|
||||
// the value in the marker filename.
|
||||
//
|
||||
// Marker is not safe for concurrent use. Multiple processes may not read or
|
||||
// move the same marker simultaneously. A Marker may only be constructed through
|
||||
// LocateMarker.
|
||||
//
|
||||
// Marker names must be unique within the directory.
|
||||
type Marker struct {
|
||||
@@ -112,11 +118,10 @@ type Marker struct {
|
||||
// monotonically increasing over the lifetime of a marker. Actual
|
||||
// marker files will always have a positive iter value.
|
||||
iter uint64
|
||||
// obsoleteFiles holds a list of files discovered by LocateMarker
|
||||
// that are old values for this marker. These files may exist if the
|
||||
// filesystem doesn't guarantee atomic renames (eg, if it's
|
||||
// implemented as a link(newpath), remove(oldpath), and a crash in
|
||||
// between may leave an entry at the old path).
|
||||
// obsoleteFiles holds a list of marker files discovered by LocateMarker that
|
||||
// are old values for this marker. These files may exist in certain error
|
||||
// cases or crashes (e.g. if the deletion of the previous marker file failed
|
||||
// during Move).
|
||||
obsoleteFiles []string
|
||||
}
|
||||
|
||||
@@ -160,22 +165,21 @@ func (a *Marker) Close() error {
|
||||
return a.dirFD.Close()
|
||||
}
|
||||
|
||||
// Move atomically moves the marker to mark the provided filename.
|
||||
// Move atomically moves the marker to a new value.
|
||||
//
|
||||
// If Move returns a nil error, the new marker value is guaranteed to be
|
||||
// persisted to stable storage. If Move returns an error, the current
|
||||
// value of the marker may be the old value or the new value. Callers
|
||||
// may retry a Move error.
|
||||
//
|
||||
// If an error occurs while syncing the directory, Move panics.
|
||||
//
|
||||
// The provided filename does not need to exist on the filesystem.
|
||||
func (a *Marker) Move(filename string) error {
|
||||
func (a *Marker) Move(newValue string) error {
|
||||
a.iter++
|
||||
dstFilename := markerFilename(a.name, a.iter, filename)
|
||||
dstFilename := markerFilename(a.name, a.iter, newValue)
|
||||
dstPath := a.fs.PathJoin(a.dir, dstFilename)
|
||||
oldFilename := a.filename
|
||||
|
||||
// The marker has never been placed. Create a new file.
|
||||
// Create the new marker.
|
||||
f, err := a.fs.Create(dstPath)
|
||||
if err != nil {
|
||||
// On a distributed filesystem, an error doesn't guarantee that
|
||||
@@ -217,7 +221,7 @@ func (a *Marker) Move(filename string) error {
|
||||
}
|
||||
|
||||
// NextIter returns the next iteration number that the marker will use.
|
||||
// Clients may use this number for formulating filenames that are
|
||||
// Clients may use this number for formulating new values that are
|
||||
// unused.
|
||||
func (a *Marker) NextIter() uint64 {
|
||||
return a.iter + 1
|
||||
@@ -226,12 +230,11 @@ func (a *Marker) NextIter() uint64 {
|
||||
// RemoveObsolete removes any obsolete files discovered while locating
|
||||
// the marker or files unable to be removed during Move.
|
||||
func (a *Marker) RemoveObsolete() error {
|
||||
obsolete := a.obsoleteFiles
|
||||
for _, filename := range obsolete {
|
||||
for i, filename := range a.obsoleteFiles {
|
||||
if err := a.fs.Remove(a.fs.PathJoin(a.dir, filename)); err != nil && !oserror.IsNotExist(err) {
|
||||
a.obsoleteFiles = a.obsoleteFiles[i:]
|
||||
return err
|
||||
}
|
||||
a.obsoleteFiles = obsolete[1:]
|
||||
}
|
||||
a.obsoleteFiles = nil
|
||||
return nil
|
||||
|
||||
34
vendor/github.com/cockroachdb/pebble/vfs/clone.go
generated
vendored
34
vendor/github.com/cockroachdb/pebble/vfs/clone.go
generated
vendored
@@ -12,8 +12,9 @@ import (
|
||||
)
|
||||
|
||||
type cloneOpts struct {
|
||||
skip func(string) bool
|
||||
sync bool
|
||||
skip func(string) bool
|
||||
sync bool
|
||||
tryLink bool
|
||||
}
|
||||
|
||||
// A CloneOption configures the behavior of Clone.
|
||||
@@ -28,6 +29,12 @@ func CloneSkip(fn func(string) bool) CloneOption {
|
||||
// CloneSync configures Clone to sync files and directories.
|
||||
var CloneSync CloneOption = func(o *cloneOpts) { o.sync = true }
|
||||
|
||||
// CloneTryLink configures Clone to link files to the destination if the source and
|
||||
// destination filesystems are the same. If the source and destination
|
||||
// filesystems are not the same or the filesystem does not support linking, then
|
||||
// Clone falls back to copying.
|
||||
var CloneTryLink CloneOption = func(o *cloneOpts) { o.tryLink = true }
|
||||
|
||||
// Clone recursively copies a directory structure from srcFS to dstFS. srcPath
|
||||
// specifies the path in srcFS to copy from and must be compatible with the
|
||||
// srcFS path format. dstDir is the target directory in dstFS and must be
|
||||
@@ -49,6 +56,7 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
stat, err := srcFile.Stat()
|
||||
if err != nil {
|
||||
@@ -56,9 +64,6 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
if err := srcFile.Close(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := dstFS.MkdirAll(dstPath, 0755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -94,13 +99,25 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// If the source and destination filesystems are the same and the user
|
||||
// specified they'd prefer to link if possible, try to use a hardlink,
|
||||
// falling back to copying if it fails.
|
||||
if srcFS == dstFS && o.tryLink {
|
||||
if err := LinkOrCopy(srcFS, srcPath, dstPath); oserror.IsNotExist(err) {
|
||||
// Clone's semantics are such that it returns (false,nil) if the
|
||||
// source does not exist.
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(srcFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := srcFile.Close(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
dstFile, err := dstFS.Create(dstPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -113,7 +130,6 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := dstFile.Close(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
132
vendor/github.com/cockroachdb/pebble/vfs/default_linux.go
generated
vendored
Normal file
132
vendor/github.com/cockroachdb/pebble/vfs/default_linux.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func wrapOSFileImpl(f *os.File) File {
|
||||
lf := &linuxFile{File: f, fd: f.Fd()}
|
||||
if lf.fd != InvalidFd {
|
||||
lf.useSyncRange = isSyncRangeSupported(lf.fd)
|
||||
}
|
||||
return lf
|
||||
}
|
||||
|
||||
func (defaultFS) OpenDir(name string) (File, error) {
|
||||
f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &linuxDir{f}, nil
|
||||
}
|
||||
|
||||
// Assert that linuxFile and linuxDir implement vfs.File.
|
||||
var (
|
||||
_ File = (*linuxDir)(nil)
|
||||
_ File = (*linuxFile)(nil)
|
||||
)
|
||||
|
||||
type linuxDir struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (d *linuxDir) Prefetch(offset int64, length int64) error { return nil }
|
||||
func (d *linuxDir) Preallocate(offset, length int64) error { return nil }
|
||||
func (d *linuxDir) SyncData() error { return d.Sync() }
|
||||
func (d *linuxDir) SyncTo(offset int64) (fullSync bool, err error) { return false, nil }
|
||||
|
||||
type linuxFile struct {
|
||||
*os.File
|
||||
fd uintptr
|
||||
useSyncRange bool
|
||||
}
|
||||
|
||||
func (f *linuxFile) Prefetch(offset int64, length int64) error {
|
||||
_, _, err := unix.Syscall(unix.SYS_READAHEAD, uintptr(f.fd), uintptr(offset), uintptr(length))
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *linuxFile) Preallocate(offset, length int64) error {
|
||||
return unix.Fallocate(int(f.fd), unix.FALLOC_FL_KEEP_SIZE, offset, length)
|
||||
}
|
||||
|
||||
func (f *linuxFile) SyncData() error {
|
||||
return unix.Fdatasync(int(f.fd))
|
||||
}
|
||||
|
||||
func (f *linuxFile) SyncTo(offset int64) (fullSync bool, err error) {
|
||||
if !f.useSyncRange {
|
||||
// Use fdatasync, which does provide persistence guarantees but won't
|
||||
// update all file metadata. From the `fdatasync` man page:
|
||||
//
|
||||
// fdatasync() is similar to fsync(), but does not flush modified
|
||||
// metadata unless that metadata is needed in order to allow a
|
||||
// subsequent data retrieval to be correctly handled. For example,
|
||||
// changes to st_atime or st_mtime (respectively, time of last access
|
||||
// and time of last modification; see stat(2)) do not require flushing
|
||||
// because they are not necessary for a subsequent data read to be
|
||||
// handled correctly. On the other hand, a change to the file size
|
||||
// (st_size, as made by say ftruncate(2)), would require a metadata
|
||||
// flush.
|
||||
if err = unix.Fdatasync(int(f.fd)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
const (
|
||||
waitBefore = 0x1
|
||||
write = 0x2
|
||||
// waitAfter = 0x4
|
||||
)
|
||||
|
||||
// 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 false, unix.SyncFileRange(int(f.fd), 0, offset, write|waitBefore)
|
||||
}
|
||||
|
||||
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, syncFn syncFileRange) bool {
|
||||
err := syncFn(int(fd), 0 /* offset */, 0 /* nbytes */, 0 /* flags */)
|
||||
return err != unix.ENOSYS
|
||||
}
|
||||
|
||||
func isSyncRangeSupported(fd uintptr) bool {
|
||||
var stat unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd), &stat); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Allowlist which filesystems we allow using sync_file_range with as some
|
||||
// filesystems treat that syscall as a noop (notably ZFS). A allowlist is
|
||||
// used instead of a denylist 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, unix.SyncFileRange)
|
||||
}
|
||||
return false
|
||||
}
|
||||
49
vendor/github.com/cockroachdb/pebble/vfs/default_unix.go
generated
vendored
Normal file
49
vendor/github.com/cockroachdb/pebble/vfs/default_unix.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd netbsd openbsd solaris
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
func wrapOSFileImpl(osFile *os.File) File {
|
||||
return &unixFile{File: osFile, fd: osFile.Fd()}
|
||||
}
|
||||
|
||||
func (defaultFS) OpenDir(name string) (File, error) {
|
||||
f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &unixFile{f, InvalidFd}, nil
|
||||
}
|
||||
|
||||
// Assert that unixFile implements vfs.File.
|
||||
var _ File = (*unixFile)(nil)
|
||||
|
||||
type unixFile struct {
|
||||
*os.File
|
||||
fd uintptr
|
||||
}
|
||||
|
||||
func (*unixFile) Prefetch(offset int64, length int64) error { return nil }
|
||||
func (*unixFile) Preallocate(offset, length int64) error { return nil }
|
||||
|
||||
func (f *unixFile) SyncData() error {
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
func (f *unixFile) SyncTo(int64) (fullSync bool, err error) {
|
||||
if err = f.Sync(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
61
vendor/github.com/cockroachdb/pebble/vfs/default_windows.go
generated
vendored
Normal file
61
vendor/github.com/cockroachdb/pebble/vfs/default_windows.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
func wrapOSFileImpl(f *os.File) File {
|
||||
return &windowsFile{f}
|
||||
}
|
||||
|
||||
func (defaultFS) OpenDir(name string) (File, error) {
|
||||
f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &windowsDir{f}, nil
|
||||
}
|
||||
|
||||
// Assert that windowsFile and windowsDir implement vfs.File.
|
||||
var (
|
||||
_ File = (*windowsFile)(nil)
|
||||
_ File = (*windowsDir)(nil)
|
||||
)
|
||||
|
||||
type windowsDir struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (*windowsDir) Prefetch(offset int64, length int64) error { return nil }
|
||||
func (*windowsDir) Preallocate(off, length int64) error { return nil }
|
||||
|
||||
// Silently ignore Sync() on Windows. This is the same behavior as
|
||||
// RocksDB. See port/win/io_win.cc:WinDirectory::Fsync().
|
||||
func (*windowsDir) Sync() error { return nil }
|
||||
func (*windowsDir) SyncData() error { return nil }
|
||||
func (*windowsDir) SyncTo(length int64) (fullSync bool, err error) { return false, nil }
|
||||
|
||||
type windowsFile struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (*windowsFile) Prefetch(offset int64, length int64) error { return nil }
|
||||
func (*windowsFile) Preallocate(offset, length int64) error { return nil }
|
||||
|
||||
func (f *windowsFile) SyncData() error { return f.Sync() }
|
||||
func (f *windowsFile) SyncTo(length int64) (fullSync bool, err error) {
|
||||
if err = f.Sync(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
20
vendor/github.com/cockroachdb/pebble/vfs/dir_unix.go
generated
vendored
20
vendor/github.com/cockroachdb/pebble/vfs/dir_unix.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
func (defaultFS) OpenDir(name string) (File, error) {
|
||||
f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
|
||||
return f, errors.WithStack(err)
|
||||
}
|
||||
33
vendor/github.com/cockroachdb/pebble/vfs/dir_windows.go
generated
vendored
33
vendor/github.com/cockroachdb/pebble/vfs/dir_windows.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
// Copyright 2014 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.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
type windowsDir struct {
|
||||
File
|
||||
}
|
||||
|
||||
func (windowsDir) Sync() error {
|
||||
// Silently ignore Sync() on Windows. This is the same behavior as
|
||||
// RocksDB. See port/win/io_win.cc:WinDirectory::Fsync().
|
||||
return nil
|
||||
}
|
||||
|
||||
func (defaultFS) OpenDir(name string) (File, error) {
|
||||
f, err := os.OpenFile(name, syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return windowsDir{f}, nil
|
||||
}
|
||||
187
vendor/github.com/cockroachdb/pebble/vfs/disk_full.go
generated
vendored
187
vendor/github.com/cockroachdb/pebble/vfs/disk_full.go
generated
vendored
@@ -39,68 +39,66 @@ func OnDiskFull(fs FS, fn func()) FS {
|
||||
}
|
||||
|
||||
type enospcFS struct {
|
||||
inner FS
|
||||
atomic struct {
|
||||
// generation is a monotonically increasing number that encodes the
|
||||
// current state of ENOSPC error handling. Incoming writes are
|
||||
// organized into generations to provide strong guarantees on when the
|
||||
// disk full callback is invoked. The callback is invoked once per
|
||||
// write generation.
|
||||
//
|
||||
// Special significance is given to the parity of this generation
|
||||
// field to optimize incoming writes in the normal state, which only
|
||||
// need to perform a single atomic load. If generation is odd, an
|
||||
// ENOSPC error is being actively handled. The generations associated
|
||||
// with writes are always even.
|
||||
//
|
||||
// The lifecycle of a write is:
|
||||
//
|
||||
// 1. Atomically load the current generation.
|
||||
// a. If it's even, this is the write's generation number.
|
||||
// b. If it's odd, an ENOSPC was recently encountered and the
|
||||
// corresponding invocation of the disk full callback has not
|
||||
// yet completed. The write must wait until the callback has
|
||||
// completed and generation is updated to an even number, which
|
||||
// becomes the write's generation number.
|
||||
// 2. Perform the write. If it encounters no error or an error other
|
||||
// than ENOSPC, the write returns and proceeds no further in this
|
||||
// lifecycle.
|
||||
// 3. Handle ENOSPC. If the write encounters ENOSPC, the callback must
|
||||
// be invoked for the write's generation. The write's goroutine
|
||||
// acquires the FS's mutex.
|
||||
// a. If the FS's current generation is still equal to the write's
|
||||
// generation, the write is the first write of its generation to
|
||||
// encounter ENOSPC. It increments the FS's current generation
|
||||
// to an odd number, signifying that an ENOSPC is being handled
|
||||
// and invokes the callback.
|
||||
// b. If the FS's current generation has changed, some other write
|
||||
// from the same generation encountered an ENOSPC first. This
|
||||
// write waits on the condition variable until the FS's current
|
||||
// generation is updated indicating that the generation's
|
||||
// callback invocation has completed.
|
||||
// 3. Retry the write once. The callback for the write's generation
|
||||
// has completed, either by this write's goroutine or another's.
|
||||
// The write may proceed with the expectation that the callback
|
||||
// remedied the full disk by freeing up disk space and an ENOSPC
|
||||
// should not be encountered again for at least a few minutes. If
|
||||
// we do encounter another ENOSPC on the retry, the callback was
|
||||
// unable to remedy the full disk and another retry won't be
|
||||
// useful. Any error, including ENOSPC, during the retry is
|
||||
// returned without further handling. None of the retries invoke
|
||||
// the callback.
|
||||
//
|
||||
// This scheme has a few nice properties:
|
||||
// * Once the disk-full callback completes, it won't be invoked
|
||||
// again unless a write that started strictly later encounters an
|
||||
// ENOSPC. This is convenient if the callback strives to 'fix' the
|
||||
// full disk, for example, by removing a ballast file. A new
|
||||
// invocation of the callback guarantees a new problem.
|
||||
// * Incoming writes block if there's an unhandled ENOSPC. Some
|
||||
// writes, like WAL or MANIFEST fsyncs, are fatal if they encounter
|
||||
// an ENOSPC.
|
||||
generation uint32
|
||||
}
|
||||
mu struct {
|
||||
inner FS
|
||||
// generation is a monotonically increasing number that encodes the
|
||||
// current state of ENOSPC error handling. Incoming writes are
|
||||
// organized into generations to provide strong guarantees on when the
|
||||
// disk full callback is invoked. The callback is invoked once per
|
||||
// write generation.
|
||||
//
|
||||
// Special significance is given to the parity of this generation
|
||||
// field to optimize incoming writes in the normal state, which only
|
||||
// need to perform a single atomic load. If generation is odd, an
|
||||
// ENOSPC error is being actively handled. The generations associated
|
||||
// with writes are always even.
|
||||
//
|
||||
// The lifecycle of a write is:
|
||||
//
|
||||
// 1. Atomically load the current generation.
|
||||
// a. If it's even, this is the write's generation number.
|
||||
// b. If it's odd, an ENOSPC was recently encountered and the
|
||||
// corresponding invocation of the disk full callback has not
|
||||
// yet completed. The write must wait until the callback has
|
||||
// completed and generation is updated to an even number, which
|
||||
// becomes the write's generation number.
|
||||
// 2. Perform the write. If it encounters no error or an error other
|
||||
// than ENOSPC, the write returns and proceeds no further in this
|
||||
// lifecycle.
|
||||
// 3. Handle ENOSPC. If the write encounters ENOSPC, the callback must
|
||||
// be invoked for the write's generation. The write's goroutine
|
||||
// acquires the FS's mutex.
|
||||
// a. If the FS's current generation is still equal to the write's
|
||||
// generation, the write is the first write of its generation to
|
||||
// encounter ENOSPC. It increments the FS's current generation
|
||||
// to an odd number, signifying that an ENOSPC is being handled
|
||||
// and invokes the callback.
|
||||
// b. If the FS's current generation has changed, some other write
|
||||
// from the same generation encountered an ENOSPC first. This
|
||||
// write waits on the condition variable until the FS's current
|
||||
// generation is updated indicating that the generation's
|
||||
// callback invocation has completed.
|
||||
// 3. Retry the write once. The callback for the write's generation
|
||||
// has completed, either by this write's goroutine or another's.
|
||||
// The write may proceed with the expectation that the callback
|
||||
// remedied the full disk by freeing up disk space and an ENOSPC
|
||||
// should not be encountered again for at least a few minutes. If
|
||||
// we do encounter another ENOSPC on the retry, the callback was
|
||||
// unable to remedy the full disk and another retry won't be
|
||||
// useful. Any error, including ENOSPC, during the retry is
|
||||
// returned without further handling. None of the retries invoke
|
||||
// the callback.
|
||||
//
|
||||
// This scheme has a few nice properties:
|
||||
// * Once the disk-full callback completes, it won't be invoked
|
||||
// again unless a write that started strictly later encounters an
|
||||
// ENOSPC. This is convenient if the callback strives to 'fix' the
|
||||
// full disk, for example, by removing a ballast file. A new
|
||||
// invocation of the callback guarantees a new problem.
|
||||
// * Incoming writes block if there's an unhandled ENOSPC. Some
|
||||
// writes, like WAL or MANIFEST fsyncs, are fatal if they encounter
|
||||
// an ENOSPC.
|
||||
generation atomic.Uint32
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
sync.Cond
|
||||
onDiskFull func()
|
||||
@@ -119,7 +117,7 @@ func (fs *enospcFS) Unwrap() FS {
|
||||
// waitUntilReady blocks until the callback returns. The returned generation
|
||||
// is always even.
|
||||
func (fs *enospcFS) waitUntilReady() uint32 {
|
||||
gen := atomic.LoadUint32(&fs.atomic.generation)
|
||||
gen := fs.generation.Load()
|
||||
if gen%2 == 0 {
|
||||
// An even generation indicates that we're not currently handling an
|
||||
// ENOSPC. Allow the write to proceed.
|
||||
@@ -132,10 +130,10 @@ func (fs *enospcFS) waitUntilReady() uint32 {
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Load the generation again with fs.mu locked.
|
||||
gen = atomic.LoadUint32(&fs.atomic.generation)
|
||||
gen = fs.generation.Load()
|
||||
for gen%2 == 1 {
|
||||
fs.mu.Wait()
|
||||
gen = atomic.LoadUint32(&fs.atomic.generation)
|
||||
gen = fs.generation.Load()
|
||||
}
|
||||
return gen
|
||||
}
|
||||
@@ -144,7 +142,7 @@ func (fs *enospcFS) handleENOSPC(gen uint32) {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
currentGeneration := atomic.LoadUint32(&fs.atomic.generation)
|
||||
currentGeneration := fs.generation.Load()
|
||||
|
||||
// If the current generation is still `gen`, this is the first goroutine
|
||||
// to hit an ENOSPC within this write generation, so this goroutine is
|
||||
@@ -153,7 +151,7 @@ func (fs *enospcFS) handleENOSPC(gen uint32) {
|
||||
// Increment the generation to an odd number, indicating that the FS
|
||||
// is out-of-disk space and incoming writes should pause and wait for
|
||||
// the next generation before continuing.
|
||||
atomic.StoreUint32(&fs.atomic.generation, gen+1)
|
||||
fs.generation.Store(gen + 1)
|
||||
|
||||
func() {
|
||||
// Drop the mutex while we invoke the callback, re-acquiring
|
||||
@@ -165,7 +163,7 @@ func (fs *enospcFS) handleENOSPC(gen uint32) {
|
||||
|
||||
// Update the current generation again to an even number, indicating
|
||||
// that the callback has completed for the write generation `gen`.
|
||||
atomic.StoreUint32(&fs.atomic.generation, gen+2)
|
||||
fs.generation.Store(gen + 2)
|
||||
fs.mu.Broadcast()
|
||||
return
|
||||
}
|
||||
@@ -185,7 +183,7 @@ func (fs *enospcFS) handleENOSPC(gen uint32) {
|
||||
// subsequent generations' callbacks.
|
||||
for currentGeneration == gen+1 {
|
||||
fs.mu.Wait()
|
||||
currentGeneration = atomic.LoadUint32(&fs.atomic.generation)
|
||||
currentGeneration = fs.generation.Load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,10 +197,10 @@ func (fs *enospcFS) Create(name string) (File, error) {
|
||||
f, err = fs.inner.Create(name)
|
||||
}
|
||||
if f != nil {
|
||||
f = WithFd(f, enospcFile{
|
||||
f = &enospcFile{
|
||||
fs: fs,
|
||||
inner: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
@@ -222,10 +220,10 @@ func (fs *enospcFS) Link(oldname, newname string) error {
|
||||
func (fs *enospcFS) Open(name string, opts ...OpenOption) (File, error) {
|
||||
f, err := fs.inner.Open(name, opts...)
|
||||
if f != nil {
|
||||
f = WithFd(f, enospcFile{
|
||||
f = &enospcFile{
|
||||
fs: fs,
|
||||
inner: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
@@ -233,10 +231,10 @@ func (fs *enospcFS) Open(name string, opts ...OpenOption) (File, error) {
|
||||
func (fs *enospcFS) OpenDir(name string) (File, error) {
|
||||
f, err := fs.inner.OpenDir(name)
|
||||
if f != nil {
|
||||
f = WithFd(f, enospcFile{
|
||||
f = &enospcFile{
|
||||
fs: fs,
|
||||
inner: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
@@ -288,10 +286,10 @@ func (fs *enospcFS) ReuseForWrite(oldname, newname string) (File, error) {
|
||||
}
|
||||
|
||||
if f != nil {
|
||||
f = WithFd(f, enospcFile{
|
||||
f = &enospcFile{
|
||||
fs: fs,
|
||||
inner: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
@@ -349,19 +347,21 @@ type enospcFile struct {
|
||||
inner File
|
||||
}
|
||||
|
||||
func (f enospcFile) Close() error {
|
||||
var _ File = (*enospcFile)(nil)
|
||||
|
||||
func (f *enospcFile) Close() error {
|
||||
return f.inner.Close()
|
||||
}
|
||||
|
||||
func (f enospcFile) Read(p []byte) (n int, err error) {
|
||||
func (f *enospcFile) Read(p []byte) (n int, err error) {
|
||||
return f.inner.Read(p)
|
||||
}
|
||||
|
||||
func (f enospcFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
func (f *enospcFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
return f.inner.ReadAt(p, off)
|
||||
}
|
||||
|
||||
func (f enospcFile) Write(p []byte) (n int, err error) {
|
||||
func (f *enospcFile) Write(p []byte) (n int, err error) {
|
||||
gen := f.fs.waitUntilReady()
|
||||
|
||||
n, err = f.inner.Write(p)
|
||||
@@ -375,11 +375,19 @@ func (f enospcFile) Write(p []byte) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (f enospcFile) Stat() (os.FileInfo, error) {
|
||||
func (f *enospcFile) Prefetch(offset, length int64) error {
|
||||
return f.inner.Prefetch(offset, length)
|
||||
}
|
||||
|
||||
func (f *enospcFile) Preallocate(offset, length int64) error {
|
||||
return f.inner.Preallocate(offset, length)
|
||||
}
|
||||
|
||||
func (f *enospcFile) Stat() (os.FileInfo, error) {
|
||||
return f.inner.Stat()
|
||||
}
|
||||
|
||||
func (f enospcFile) Sync() error {
|
||||
func (f *enospcFile) Sync() error {
|
||||
gen := f.fs.waitUntilReady()
|
||||
|
||||
err := f.inner.Sync()
|
||||
@@ -399,7 +407,18 @@ func (f enospcFile) Sync() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that *enospcFS implements the FS interface.
|
||||
func (f *enospcFile) SyncData() error {
|
||||
return f.inner.SyncData()
|
||||
}
|
||||
|
||||
func (f *enospcFile) SyncTo(length int64) (fullSync bool, err error) {
|
||||
return f.inner.SyncTo(length)
|
||||
}
|
||||
|
||||
func (f *enospcFile) Fd() uintptr {
|
||||
return f.inner.Fd()
|
||||
}
|
||||
|
||||
var _ FS = (*enospcFS)(nil)
|
||||
|
||||
func isENOSPC(err error) bool {
|
||||
|
||||
394
vendor/github.com/cockroachdb/pebble/vfs/disk_health.go
generated
vendored
394
vendor/github.com/cockroachdb/pebble/vfs/disk_health.go
generated
vendored
@@ -5,25 +5,102 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultTickInterval is the default interval between two ticks of each
|
||||
// diskHealthCheckingFile loop iteration.
|
||||
defaultTickInterval = 2 * time.Second
|
||||
// preallocatedSlotCount is the default number of slots available for
|
||||
// concurrent filesystem operations. The slot count may be exceeded, but
|
||||
// each additional slot will incur an additional allocation. We choose 16
|
||||
// here with the expectation that it is significantly more than required in
|
||||
// practice. See the comment above the diskHealthCheckingFS type definition.
|
||||
preallocatedSlotCount = 16
|
||||
// deltaBits is the number of bits in the packed 64-bit integer used for
|
||||
// identifying a delta from the file creation time in milliseconds.
|
||||
deltaBits = 40
|
||||
// writeSizeBits is the number of bits in the packed 64-bit integer used for
|
||||
// identifying the size of the write operation, if the operation is sized. See
|
||||
// writeSizePrecision below for precision of size.
|
||||
writeSizeBits = 20
|
||||
// Track size of writes at kilobyte precision. See comment above lastWritePacked for more.
|
||||
writeSizePrecision = 1024
|
||||
)
|
||||
|
||||
// Variables to enable testing.
|
||||
var (
|
||||
// defaultTickInterval is the default interval between two ticks of each
|
||||
// diskHealthCheckingFile loop iteration.
|
||||
defaultTickInterval = 2 * time.Second
|
||||
)
|
||||
|
||||
// OpType is the type of IO operation being monitored by a
|
||||
// diskHealthCheckingFile.
|
||||
type OpType uint8
|
||||
|
||||
// The following OpTypes is limited to the subset of file system operations that
|
||||
// a diskHealthCheckingFile supports (namely writes and syncs).
|
||||
const (
|
||||
OpTypeUnknown OpType = iota
|
||||
OpTypeWrite
|
||||
OpTypeSync
|
||||
OpTypeSyncData
|
||||
OpTypeSyncTo
|
||||
OpTypeCreate
|
||||
OpTypeLink
|
||||
OpTypeMkdirAll
|
||||
OpTypePreallocate
|
||||
OpTypeRemove
|
||||
OpTypeRemoveAll
|
||||
OpTypeRename
|
||||
OpTypeReuseForWrite
|
||||
// Note: opTypeMax is just used in tests. It must appear last in the list
|
||||
// of OpTypes.
|
||||
opTypeMax
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (o OpType) String() string {
|
||||
switch o {
|
||||
case OpTypeWrite:
|
||||
return "write"
|
||||
case OpTypeSync:
|
||||
return "sync"
|
||||
case OpTypeSyncData:
|
||||
return "syncdata"
|
||||
case OpTypeSyncTo:
|
||||
return "syncto"
|
||||
case OpTypeCreate:
|
||||
return "create"
|
||||
case OpTypeLink:
|
||||
return "link"
|
||||
case OpTypeMkdirAll:
|
||||
return "mkdirall"
|
||||
case OpTypePreallocate:
|
||||
return "preallocate"
|
||||
case OpTypeRemove:
|
||||
return "remove"
|
||||
case OpTypeRemoveAll:
|
||||
return "removall"
|
||||
case OpTypeRename:
|
||||
return "rename"
|
||||
case OpTypeReuseForWrite:
|
||||
return "reuseforwrite"
|
||||
case OpTypeUnknown:
|
||||
return "unknown"
|
||||
default:
|
||||
panic(fmt.Sprintf("vfs: unknown op type: %d", o))
|
||||
}
|
||||
}
|
||||
|
||||
// diskHealthCheckingFile is a File wrapper to detect slow disk operations, and
|
||||
// call onSlowDisk if a disk operation is seen to exceed diskSlowThreshold.
|
||||
//
|
||||
@@ -32,28 +109,51 @@ const (
|
||||
// duration. This setup is preferable to creating a new timer at every disk
|
||||
// operation, as it reduces overhead per disk operation.
|
||||
type diskHealthCheckingFile struct {
|
||||
File
|
||||
|
||||
onSlowDisk func(time.Duration)
|
||||
file File
|
||||
onSlowDisk func(opType OpType, writeSizeInBytes int, duration time.Duration)
|
||||
diskSlowThreshold time.Duration
|
||||
tickInterval time.Duration
|
||||
|
||||
stopper chan struct{}
|
||||
lastWriteNanos int64
|
||||
stopper chan struct{}
|
||||
// lastWritePacked is a 64-bit unsigned int. The most significant
|
||||
// 40 bits represent an delta (in milliseconds) from the creation
|
||||
// time of the diskHealthCheckingFile. The next most significant 20 bits
|
||||
// represent the size of the write in KBs, if the write has a size. (If
|
||||
// it doesn't, the 20 bits are zeroed). The least significant four bits
|
||||
// contains the OpType.
|
||||
//
|
||||
// The use of 40 bits for an delta provides ~34 years of effective
|
||||
// monitoring time before the uint wraps around, at millisecond precision.
|
||||
// ~34 years of process uptime "ought to be enough for anybody". Millisecond
|
||||
// writeSizePrecision is sufficient, given that we are monitoring for writes that take
|
||||
// longer than one millisecond.
|
||||
//
|
||||
// The use of 20 bits for the size in KBs allows representing sizes up
|
||||
// to nearly one GB. If the write is larger than that, we round down to ~one GB.
|
||||
//
|
||||
// The use of four bits for OpType allows for 16 operation types.
|
||||
//
|
||||
// NB: this packing scheme is not persisted, and is therefore safe to adjust
|
||||
// across process boundaries.
|
||||
lastWritePacked uint64
|
||||
createTime time.Time
|
||||
}
|
||||
|
||||
// newDiskHealthCheckingFile instantiates a new diskHealthCheckingFile, with the
|
||||
// specified time threshold and event listener.
|
||||
func newDiskHealthCheckingFile(
|
||||
file File, diskSlowThreshold time.Duration, onSlowDisk func(time.Duration),
|
||||
file File,
|
||||
diskSlowThreshold time.Duration,
|
||||
onSlowDisk func(OpType OpType, writeSizeInBytes int, duration time.Duration),
|
||||
) *diskHealthCheckingFile {
|
||||
return &diskHealthCheckingFile{
|
||||
File: file,
|
||||
file: file,
|
||||
onSlowDisk: onSlowDisk,
|
||||
diskSlowThreshold: diskSlowThreshold,
|
||||
tickInterval: defaultTickInterval,
|
||||
|
||||
stopper: make(chan struct{}),
|
||||
stopper: make(chan struct{}),
|
||||
createTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,16 +174,17 @@ func (d *diskHealthCheckingFile) startTicker() {
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
lastWriteNanos := atomic.LoadInt64(&d.lastWriteNanos)
|
||||
if lastWriteNanos == 0 {
|
||||
packed := atomic.LoadUint64(&d.lastWritePacked)
|
||||
if packed == 0 {
|
||||
continue
|
||||
}
|
||||
lastWrite := time.Unix(0, lastWriteNanos)
|
||||
delta, writeSize, op := unpack(packed)
|
||||
lastWrite := d.createTime.Add(delta)
|
||||
now := time.Now()
|
||||
if lastWrite.Add(d.diskSlowThreshold).Before(now) {
|
||||
// diskSlowThreshold was exceeded. Call the passed-in
|
||||
// listener.
|
||||
d.onSlowDisk(now.Sub(lastWrite))
|
||||
d.onSlowDisk(op, writeSize, now.Sub(lastWrite))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,10 +196,25 @@ func (d *diskHealthCheckingFile) stopTicker() {
|
||||
close(d.stopper)
|
||||
}
|
||||
|
||||
// Fd implements (vfs.File).Fd.
|
||||
func (d *diskHealthCheckingFile) Fd() uintptr {
|
||||
return d.file.Fd()
|
||||
}
|
||||
|
||||
// Read implements (vfs.File).Read
|
||||
func (d *diskHealthCheckingFile) Read(p []byte) (int, error) {
|
||||
return d.file.Read(p)
|
||||
}
|
||||
|
||||
// ReadAt implements (vfs.File).ReadAt
|
||||
func (d *diskHealthCheckingFile) ReadAt(p []byte, off int64) (int, error) {
|
||||
return d.file.ReadAt(p, off)
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface.
|
||||
func (d *diskHealthCheckingFile) Write(p []byte) (n int, err error) {
|
||||
d.timeDiskOp(func() {
|
||||
n, err = d.File.Write(p)
|
||||
d.timeDiskOp(OpTypeWrite, int64(len(p)), func() {
|
||||
n, err = d.file.Write(p)
|
||||
})
|
||||
return n, err
|
||||
}
|
||||
@@ -106,32 +222,164 @@ func (d *diskHealthCheckingFile) Write(p []byte) (n int, err error) {
|
||||
// Close implements the io.Closer interface.
|
||||
func (d *diskHealthCheckingFile) Close() error {
|
||||
d.stopTicker()
|
||||
return d.File.Close()
|
||||
return d.file.Close()
|
||||
}
|
||||
|
||||
// Sync implements the io.Syncer interface.
|
||||
func (d *diskHealthCheckingFile) Sync() (err error) {
|
||||
d.timeDiskOp(func() {
|
||||
err = d.File.Sync()
|
||||
// Prefetch implements (vfs.File).Prefetch.
|
||||
func (d *diskHealthCheckingFile) Prefetch(offset, length int64) error {
|
||||
return d.file.Prefetch(offset, length)
|
||||
}
|
||||
|
||||
// Preallocate implements (vfs.File).Preallocate.
|
||||
func (d *diskHealthCheckingFile) Preallocate(off, n int64) (err error) {
|
||||
d.timeDiskOp(OpTypePreallocate, n, func() {
|
||||
err = d.file.Preallocate(off, n)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Stat implements (vfs.File).Stat.
|
||||
func (d *diskHealthCheckingFile) Stat() (os.FileInfo, error) {
|
||||
return d.file.Stat()
|
||||
}
|
||||
|
||||
// Sync implements the io.Syncer interface.
|
||||
func (d *diskHealthCheckingFile) Sync() (err error) {
|
||||
d.timeDiskOp(OpTypeSync, 0, func() {
|
||||
err = d.file.Sync()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// SyncData implements (vfs.File).SyncData.
|
||||
func (d *diskHealthCheckingFile) SyncData() (err error) {
|
||||
d.timeDiskOp(OpTypeSyncData, 0, func() {
|
||||
err = d.file.SyncData()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// SyncTo implements (vfs.File).SyncTo.
|
||||
func (d *diskHealthCheckingFile) SyncTo(length int64) (fullSync bool, err error) {
|
||||
d.timeDiskOp(OpTypeSyncTo, length, func() {
|
||||
fullSync, err = d.file.SyncTo(length)
|
||||
})
|
||||
return fullSync, err
|
||||
}
|
||||
|
||||
// timeDiskOp runs the specified closure and makes its timing visible to the
|
||||
// monitoring goroutine, in case it exceeds one of the slow disk durations.
|
||||
func (d *diskHealthCheckingFile) timeDiskOp(op func()) {
|
||||
// opType should always be set. writeSizeInBytes should be set if the write
|
||||
// operation is sized. If not, it should be set to zero.
|
||||
func (d *diskHealthCheckingFile) timeDiskOp(opType OpType, writeSizeInBytes int64, op func()) {
|
||||
if d == nil {
|
||||
op()
|
||||
return
|
||||
}
|
||||
|
||||
atomic.StoreInt64(&d.lastWriteNanos, time.Now().UnixNano())
|
||||
delta := time.Since(d.createTime)
|
||||
packed := pack(delta, writeSizeInBytes, opType)
|
||||
if invariants.Enabled {
|
||||
if !atomic.CompareAndSwapUint64(&d.lastWritePacked, 0, packed) {
|
||||
panic("concurrent write operations detected on file")
|
||||
}
|
||||
} else {
|
||||
atomic.StoreUint64(&d.lastWritePacked, packed)
|
||||
}
|
||||
defer func() {
|
||||
atomic.StoreInt64(&d.lastWriteNanos, 0)
|
||||
if invariants.Enabled {
|
||||
if !atomic.CompareAndSwapUint64(&d.lastWritePacked, packed, 0) {
|
||||
panic("concurrent write operations detected on file")
|
||||
}
|
||||
} else {
|
||||
atomic.StoreUint64(&d.lastWritePacked, 0)
|
||||
}
|
||||
}()
|
||||
op()
|
||||
}
|
||||
|
||||
// Note the slight lack of symmetry between pack & unpack. pack takes an int64 for writeSizeInBytes, since
|
||||
// callers of pack use an int64. This is dictated by the vfs interface. unpack OTOH returns an int. This is
|
||||
// safe because the packing scheme implies we only actually need 32 bits.
|
||||
func pack(delta time.Duration, writeSizeInBytes int64, opType OpType) uint64 {
|
||||
// We have no guarantee of clock monotonicity. If we have a small regression
|
||||
// in the clock, we set deltaMillis to zero, so we can still catch the operation
|
||||
// if happens to be slow.
|
||||
deltaMillis := delta.Milliseconds()
|
||||
if deltaMillis < 0 {
|
||||
deltaMillis = 0
|
||||
}
|
||||
// As of 3/7/2023, the use of 40 bits for an delta provides ~34 years
|
||||
// of effective monitoring time before the uint wraps around, at millisecond
|
||||
// precision.
|
||||
if deltaMillis > 1<<deltaBits-1 {
|
||||
panic("vfs: last write delta would result in integer wraparound")
|
||||
}
|
||||
|
||||
// See writeSizePrecision to get the unit of writeSize. As of 1/26/2023, the unit is KBs.
|
||||
writeSize := writeSizeInBytes / writeSizePrecision
|
||||
// If the size of the write is larger than we can store in the packed int, store the max
|
||||
// value we can store in the packed int.
|
||||
const writeSizeCeiling = 1<<writeSizeBits - 1
|
||||
if writeSize > writeSizeCeiling {
|
||||
writeSize = writeSizeCeiling
|
||||
}
|
||||
|
||||
return uint64(deltaMillis)<<(64-deltaBits) | uint64(writeSize)<<(64-deltaBits-writeSizeBits) | uint64(opType)
|
||||
}
|
||||
|
||||
func unpack(packed uint64) (delta time.Duration, writeSizeInBytes int, opType OpType) {
|
||||
delta = time.Duration(packed>>(64-deltaBits)) * time.Millisecond
|
||||
wz := int64(packed>>(64-deltaBits-writeSizeBits)) & ((1 << writeSizeBits) - 1) * writeSizePrecision
|
||||
// Given the packing scheme, converting wz to an int will not truncate anything.
|
||||
writeSizeInBytes = int(wz)
|
||||
opType = OpType(packed & 0xf)
|
||||
return delta, writeSizeInBytes, opType
|
||||
}
|
||||
|
||||
// diskHealthCheckingDir implements disk-health checking for directories. Unlike
|
||||
// other files, we allow directories to receive concurrent write operations
|
||||
// (Syncs are the only write operations supported by a directory.) Since the
|
||||
// diskHealthCheckingFile's timeDiskOp can only track a single in-flight
|
||||
// operation at a time, we time the operation using the filesystem-level
|
||||
// timeFilesystemOp function instead.
|
||||
type diskHealthCheckingDir struct {
|
||||
File
|
||||
name string
|
||||
fs *diskHealthCheckingFS
|
||||
}
|
||||
|
||||
// Sync implements the io.Syncer interface.
|
||||
func (d *diskHealthCheckingDir) Sync() (err error) {
|
||||
d.fs.timeFilesystemOp(d.name, OpTypeSync, func() {
|
||||
err = d.File.Sync()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// DiskSlowInfo captures info about detected slow operations on the vfs.
|
||||
type DiskSlowInfo struct {
|
||||
// Path of file being written to.
|
||||
Path string
|
||||
// Operation being performed on the file.
|
||||
OpType OpType
|
||||
// Size of write in bytes, if the write is sized.
|
||||
WriteSize int
|
||||
// Duration that has elapsed since this disk operation started.
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func (i DiskSlowInfo) String() string {
|
||||
return redact.StringWithoutMarkers(i)
|
||||
}
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (i DiskSlowInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("disk slowness detected: %s on file %s (%d bytes) has been ongoing for %0.1fs",
|
||||
redact.Safe(i.OpType.String()), redact.Safe(filepath.Base(i.Path)),
|
||||
redact.Safe(i.WriteSize), redact.Safe(i.Duration.Seconds()))
|
||||
}
|
||||
|
||||
// diskHealthCheckingFS adds disk-health checking facilities to a VFS.
|
||||
// It times disk write operations in two ways:
|
||||
//
|
||||
@@ -156,22 +404,22 @@ func (d *diskHealthCheckingFile) timeDiskOp(op func()) {
|
||||
// maximum concurrent filesystem operations. This is expected to be very few
|
||||
// for these reasons:
|
||||
// 1. Pebble has limited write concurrency. Flushes, compactions and WAL
|
||||
// rotations are the primary sources of filesystem metadata operations. With
|
||||
// the default max-compaction concurrency, these operations require at most 5
|
||||
// concurrent slots if all 5 perform a filesystem metadata operation
|
||||
// simultaneously.
|
||||
// rotations are the primary sources of filesystem metadata operations. With
|
||||
// the default max-compaction concurrency, these operations require at most 5
|
||||
// concurrent slots if all 5 perform a filesystem metadata operation
|
||||
// simultaneously.
|
||||
// 2. Pebble's limited concurrent I/O writers spend most of their time
|
||||
// performing file I/O, not performing the filesystem metadata operations that
|
||||
// require recording a slot on the diskHealthCheckingFS.
|
||||
// performing file I/O, not performing the filesystem metadata operations that
|
||||
// require recording a slot on the diskHealthCheckingFS.
|
||||
// 3. In CockroachDB, each additional store/Pebble instance has its own vfs.FS
|
||||
// which provides a separate goroutine and set of slots.
|
||||
// which provides a separate goroutine and set of slots.
|
||||
// 4. In CockroachDB, many of the additional sources of filesystem metadata
|
||||
// operations (like encryption-at-rest) are sequential with respect to Pebble's
|
||||
// threads.
|
||||
// operations (like encryption-at-rest) are sequential with respect to Pebble's
|
||||
// threads.
|
||||
type diskHealthCheckingFS struct {
|
||||
tickInterval time.Duration
|
||||
diskSlowThreshold time.Duration
|
||||
onSlowDisk func(string, time.Duration)
|
||||
onSlowDisk func(DiskSlowInfo)
|
||||
fs FS
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
@@ -194,6 +442,7 @@ type diskHealthCheckingFS struct {
|
||||
|
||||
type slot struct {
|
||||
name string
|
||||
opType OpType
|
||||
startNanos int64
|
||||
}
|
||||
|
||||
@@ -207,7 +456,7 @@ var _ FS = (*diskHealthCheckingFS)(nil)
|
||||
//
|
||||
// A threshold of zero disables disk-health checking.
|
||||
func WithDiskHealthChecks(
|
||||
innerFS FS, diskSlowThreshold time.Duration, onSlowDisk func(string, time.Duration),
|
||||
innerFS FS, diskSlowThreshold time.Duration, onSlowDisk func(info DiskSlowInfo),
|
||||
) (FS, io.Closer) {
|
||||
if diskSlowThreshold == 0 {
|
||||
return innerFS, noopCloser{}
|
||||
@@ -230,7 +479,7 @@ func WithDiskHealthChecks(
|
||||
return fs, fs
|
||||
}
|
||||
|
||||
func (d *diskHealthCheckingFS) timeFilesystemOp(name string, op func()) {
|
||||
func (d *diskHealthCheckingFS) timeFilesystemOp(name string, opType OpType, op func()) {
|
||||
if d == nil {
|
||||
op()
|
||||
return
|
||||
@@ -265,6 +514,7 @@ func (d *diskHealthCheckingFS) timeFilesystemOp(name string, op func()) {
|
||||
// This slot is not in use. Claim it.
|
||||
s = d.mu.inflight[i]
|
||||
s.name = name
|
||||
s.opType = opType
|
||||
atomic.StoreInt64(&s.startNanos, startNanos)
|
||||
break
|
||||
}
|
||||
@@ -277,6 +527,7 @@ func (d *diskHealthCheckingFS) timeFilesystemOp(name string, op func()) {
|
||||
if s == nil {
|
||||
s = &slot{
|
||||
name: name,
|
||||
opType: opType,
|
||||
startNanos: startNanos,
|
||||
}
|
||||
d.mu.inflight = append(d.mu.inflight, s)
|
||||
@@ -297,23 +548,38 @@ func (d *diskHealthCheckingFS) startTickerLocked() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(d.tickInterval)
|
||||
defer ticker.Stop()
|
||||
var exceededSlots []slot
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Scan the inflight slots for any slots recording a start
|
||||
// time older than the diskSlowThreshold.
|
||||
exceededSlots = exceededSlots[:0]
|
||||
d.mu.Lock()
|
||||
now := time.Now()
|
||||
for i := range d.mu.inflight {
|
||||
nanos := atomic.LoadInt64(&d.mu.inflight[i].startNanos)
|
||||
if nanos != 0 && time.Unix(0, nanos).Add(d.diskSlowThreshold).Before(now) {
|
||||
// diskSlowThreshold was exceeded. Invoke the provided
|
||||
// callback.
|
||||
d.onSlowDisk(d.mu.inflight[i].name, now.Sub(time.Unix(0, nanos)))
|
||||
// diskSlowThreshold was exceeded. Copy this inflightOp into
|
||||
// exceededSlots and call d.onSlowDisk after dropping the mutex.
|
||||
var inflightOp slot
|
||||
inflightOp.name = d.mu.inflight[i].name
|
||||
inflightOp.opType = d.mu.inflight[i].opType
|
||||
inflightOp.startNanos = nanos
|
||||
exceededSlots = append(exceededSlots, inflightOp)
|
||||
}
|
||||
}
|
||||
d.mu.Unlock()
|
||||
for i := range exceededSlots {
|
||||
d.onSlowDisk(
|
||||
DiskSlowInfo{
|
||||
Path: exceededSlots[i].name,
|
||||
OpType: exceededSlots[i].opType,
|
||||
WriteSize: 0, // writes at the fs level are not sized
|
||||
Duration: now.Sub(time.Unix(0, exceededSlots[i].startNanos)),
|
||||
})
|
||||
}
|
||||
case <-stopper:
|
||||
return
|
||||
}
|
||||
@@ -354,7 +620,7 @@ func (d *diskHealthCheckingFS) Close() error {
|
||||
func (d *diskHealthCheckingFS) Create(name string) (File, error) {
|
||||
var f File
|
||||
var err error
|
||||
d.timeFilesystemOp(name, func() {
|
||||
d.timeFilesystemOp(name, OpTypeCreate, func() {
|
||||
f, err = d.fs.Create(name)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -363,11 +629,17 @@ func (d *diskHealthCheckingFS) Create(name string) (File, error) {
|
||||
if d.diskSlowThreshold == 0 {
|
||||
return f, nil
|
||||
}
|
||||
checkingFile := newDiskHealthCheckingFile(f, d.diskSlowThreshold, func(duration time.Duration) {
|
||||
d.onSlowDisk(name, duration)
|
||||
checkingFile := newDiskHealthCheckingFile(f, d.diskSlowThreshold, func(opType OpType, writeSizeInBytes int, duration time.Duration) {
|
||||
d.onSlowDisk(
|
||||
DiskSlowInfo{
|
||||
Path: name,
|
||||
OpType: opType,
|
||||
WriteSize: writeSizeInBytes,
|
||||
Duration: duration,
|
||||
})
|
||||
})
|
||||
checkingFile.startTicker()
|
||||
return WithFd(f, checkingFile), nil
|
||||
return checkingFile, nil
|
||||
}
|
||||
|
||||
// GetDiskUsage implements the FS interface.
|
||||
@@ -378,7 +650,7 @@ func (d *diskHealthCheckingFS) GetDiskUsage(path string) (DiskUsage, error) {
|
||||
// Link implements the FS interface.
|
||||
func (d *diskHealthCheckingFS) Link(oldname, newname string) error {
|
||||
var err error
|
||||
d.timeFilesystemOp(newname, func() {
|
||||
d.timeFilesystemOp(newname, OpTypeLink, func() {
|
||||
err = d.fs.Link(oldname, newname)
|
||||
})
|
||||
return err
|
||||
@@ -397,7 +669,7 @@ func (d *diskHealthCheckingFS) Lock(name string) (io.Closer, error) {
|
||||
// MkdirAll implements the FS interface.
|
||||
func (d *diskHealthCheckingFS) MkdirAll(dir string, perm os.FileMode) error {
|
||||
var err error
|
||||
d.timeFilesystemOp(dir, func() {
|
||||
d.timeFilesystemOp(dir, OpTypeMkdirAll, func() {
|
||||
err = d.fs.MkdirAll(dir, perm)
|
||||
})
|
||||
return err
|
||||
@@ -416,11 +688,11 @@ func (d *diskHealthCheckingFS) OpenDir(name string) (File, error) {
|
||||
}
|
||||
// Directories opened with OpenDir must be opened with health checking,
|
||||
// because they may be explicitly synced.
|
||||
checkingFile := newDiskHealthCheckingFile(f, d.diskSlowThreshold, func(duration time.Duration) {
|
||||
d.onSlowDisk(name, duration)
|
||||
})
|
||||
checkingFile.startTicker()
|
||||
return WithFd(f, checkingFile), nil
|
||||
return &diskHealthCheckingDir{
|
||||
File: f,
|
||||
name: name,
|
||||
fs: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PathBase implements the FS interface.
|
||||
@@ -441,7 +713,7 @@ func (d *diskHealthCheckingFS) PathDir(path string) string {
|
||||
// Remove implements the FS interface.
|
||||
func (d *diskHealthCheckingFS) Remove(name string) error {
|
||||
var err error
|
||||
d.timeFilesystemOp(name, func() {
|
||||
d.timeFilesystemOp(name, OpTypeRemove, func() {
|
||||
err = d.fs.Remove(name)
|
||||
})
|
||||
return err
|
||||
@@ -450,7 +722,7 @@ func (d *diskHealthCheckingFS) Remove(name string) error {
|
||||
// RemoveAll implements the FS interface.
|
||||
func (d *diskHealthCheckingFS) RemoveAll(name string) error {
|
||||
var err error
|
||||
d.timeFilesystemOp(name, func() {
|
||||
d.timeFilesystemOp(name, OpTypeRemoveAll, func() {
|
||||
err = d.fs.RemoveAll(name)
|
||||
})
|
||||
return err
|
||||
@@ -459,7 +731,7 @@ func (d *diskHealthCheckingFS) RemoveAll(name string) error {
|
||||
// Rename implements the FS interface.
|
||||
func (d *diskHealthCheckingFS) Rename(oldname, newname string) error {
|
||||
var err error
|
||||
d.timeFilesystemOp(newname, func() {
|
||||
d.timeFilesystemOp(newname, OpTypeRename, func() {
|
||||
err = d.fs.Rename(oldname, newname)
|
||||
})
|
||||
return err
|
||||
@@ -469,7 +741,7 @@ func (d *diskHealthCheckingFS) Rename(oldname, newname string) error {
|
||||
func (d *diskHealthCheckingFS) ReuseForWrite(oldname, newname string) (File, error) {
|
||||
var f File
|
||||
var err error
|
||||
d.timeFilesystemOp(newname, func() {
|
||||
d.timeFilesystemOp(newname, OpTypeReuseForWrite, func() {
|
||||
f, err = d.fs.ReuseForWrite(oldname, newname)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -478,11 +750,17 @@ func (d *diskHealthCheckingFS) ReuseForWrite(oldname, newname string) (File, err
|
||||
if d.diskSlowThreshold == 0 {
|
||||
return f, nil
|
||||
}
|
||||
checkingFile := newDiskHealthCheckingFile(f, d.diskSlowThreshold, func(duration time.Duration) {
|
||||
d.onSlowDisk(newname, duration)
|
||||
checkingFile := newDiskHealthCheckingFile(f, d.diskSlowThreshold, func(opType OpType, writeSizeInBytes int, duration time.Duration) {
|
||||
d.onSlowDisk(
|
||||
DiskSlowInfo{
|
||||
Path: newname,
|
||||
OpType: opType,
|
||||
WriteSize: writeSizeInBytes,
|
||||
Duration: duration,
|
||||
})
|
||||
})
|
||||
checkingFile.startTicker()
|
||||
return WithFd(f, checkingFile), nil
|
||||
return checkingFile, nil
|
||||
}
|
||||
|
||||
// Stat implements the FS interface.
|
||||
|
||||
26
vendor/github.com/cockroachdb/pebble/vfs/disk_usage_netbsd.go
generated
vendored
Normal file
26
vendor/github.com/cockroachdb/pebble/vfs/disk_usage_netbsd.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//go:build netbsd
|
||||
// +build netbsd
|
||||
|
||||
package vfs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func (defaultFS) GetDiskUsage(path string) (DiskUsage, error) {
|
||||
stat := unix.Statvfs_t{}
|
||||
if err := unix.Statvfs(path, &stat); err != nil {
|
||||
return DiskUsage{}, err
|
||||
}
|
||||
|
||||
freeBytes := uint64(stat.Bsize) * uint64(stat.Bfree)
|
||||
availBytes := uint64(stat.Bsize) * uint64(stat.Bavail)
|
||||
totalBytes := uint64(stat.Bsize) * uint64(stat.Blocks)
|
||||
return DiskUsage{
|
||||
AvailBytes: availBytes,
|
||||
TotalBytes: totalBytes,
|
||||
UsedBytes: totalBytes - freeBytes,
|
||||
}, nil
|
||||
}
|
||||
8
vendor/github.com/cockroachdb/pebble/vfs/errors_unix.go
generated
vendored
8
vendor/github.com/cockroachdb/pebble/vfs/errors_unix.go
generated
vendored
@@ -2,19 +2,17 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || linux || openbsd
|
||||
// +build darwin dragonfly freebsd linux openbsd
|
||||
//go:build darwin || dragonfly || freebsd || linux || openbsd || netbsd
|
||||
// +build darwin dragonfly freebsd linux openbsd netbsd
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var errNotEmpty = syscall.ENOTEMPTY
|
||||
var errNotEmpty = unix.ENOTEMPTY
|
||||
|
||||
// IsNoSpaceError returns true if the given error indicates that the disk is
|
||||
// out of space.
|
||||
|
||||
42
vendor/github.com/cockroachdb/pebble/vfs/fd.go
generated
vendored
42
vendor/github.com/cockroachdb/pebble/vfs/fd.go
generated
vendored
@@ -1,42 +0,0 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
package vfs
|
||||
|
||||
// fdGetter is an interface for a file with an Fd() method. A lot of
|
||||
// File related optimizations (eg. Prefetch(), WAL recycling) rely on the
|
||||
// existence of the Fd method to return a raw file descriptor.
|
||||
type fdGetter interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// fdFileWrapper is a File wrapper that also exposes an Fd() method. Used to
|
||||
// wrap outer (wrapped) Files that could unintentionally hide the Fd() method
|
||||
// exposed by the inner (unwrapped) File. It effectively lets the Fd() method
|
||||
// bypass the outer File and go to the inner File.
|
||||
type fdFileWrapper struct {
|
||||
File
|
||||
|
||||
// All methods usually pass through to File above, except for Fd(), which
|
||||
// bypasses it and gets called directly on the inner file.
|
||||
inner fdGetter
|
||||
}
|
||||
|
||||
func (f *fdFileWrapper) Fd() uintptr {
|
||||
return f.inner.Fd()
|
||||
}
|
||||
|
||||
// WithFd takes an inner (unwrapped) and an outer (wrapped) vfs.File,
|
||||
// and returns an fdFileWrapper if the inner file has an Fd() method. Use this
|
||||
// method to fix the hiding of the Fd() method and the subsequent unintentional
|
||||
// disabling of Fd-related file optimizations.
|
||||
func WithFd(inner, outer File) File {
|
||||
if f, ok := inner.(fdGetter); ok {
|
||||
return &fdFileWrapper{
|
||||
File: outer,
|
||||
inner: f,
|
||||
}
|
||||
}
|
||||
return outer
|
||||
}
|
||||
8
vendor/github.com/cockroachdb/pebble/vfs/file_lock_unix.go
generated
vendored
8
vendor/github.com/cockroachdb/pebble/vfs/file_lock_unix.go
generated
vendored
@@ -11,9 +11,9 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var lockedFiles struct {
|
||||
@@ -54,14 +54,14 @@ func (defaultFS) Lock(name string) (io.Closer, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
spec := unix.Flock_t{
|
||||
Type: unix.F_WRLCK,
|
||||
Whence: io.SeekStart,
|
||||
Start: 0,
|
||||
Len: 0, // 0 means to lock the entire file.
|
||||
Pid: int32(os.Getpid()),
|
||||
}
|
||||
if err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &spec); err != nil {
|
||||
if err := unix.FcntlFlock(f.Fd(), unix.F_SETLK, &spec); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
19
vendor/github.com/cockroachdb/pebble/vfs/file_lock_windows.go
generated
vendored
19
vendor/github.com/cockroachdb/pebble/vfs/file_lock_windows.go
generated
vendored
@@ -9,29 +9,30 @@ package vfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// lockCloser hides all of an syscall.Handle's methods, except for Close.
|
||||
// lockCloser hides all of an windows.Handle's methods, except for Close.
|
||||
type lockCloser struct {
|
||||
fd syscall.Handle
|
||||
fd windows.Handle
|
||||
}
|
||||
|
||||
func (l lockCloser) Close() error {
|
||||
return syscall.Close(l.fd)
|
||||
return windows.Close(l.fd)
|
||||
}
|
||||
|
||||
// Lock locks the given file. On Windows, Locking will fail if the file is
|
||||
// already open by the current process.
|
||||
func (defaultFS) Lock(name string) (io.Closer, error) {
|
||||
p, err := syscall.UTF16PtrFromString(name)
|
||||
p, err := windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, err := syscall.CreateFile(p,
|
||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||
0, nil, syscall.CREATE_ALWAYS,
|
||||
syscall.FILE_ATTRIBUTE_NORMAL,
|
||||
fd, err := windows.CreateFile(p,
|
||||
windows.GENERIC_READ|windows.GENERIC_WRITE,
|
||||
0, nil, windows.CREATE_ALWAYS,
|
||||
windows.FILE_ATTRIBUTE_NORMAL,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
143
vendor/github.com/cockroachdb/pebble/vfs/logging_fs.go
generated
vendored
Normal file
143
vendor/github.com/cockroachdb/pebble/vfs/logging_fs.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// WithLogging wraps an FS and logs filesystem modification operations to the
|
||||
// given logFn.
|
||||
func WithLogging(fs FS, logFn LogFn) FS {
|
||||
return &loggingFS{
|
||||
FS: fs,
|
||||
logFn: logFn,
|
||||
}
|
||||
}
|
||||
|
||||
// LogFn is a function that is used to capture a log when WithLogging is used.
|
||||
type LogFn func(fmt string, args ...interface{})
|
||||
|
||||
type loggingFS struct {
|
||||
FS
|
||||
logFn LogFn
|
||||
}
|
||||
|
||||
var _ FS = (*loggingFS)(nil)
|
||||
|
||||
func (fs *loggingFS) Create(name string) (File, error) {
|
||||
fs.logFn("create: %s", name)
|
||||
f, err := fs.FS.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newLoggingFile(f, name, fs.logFn), nil
|
||||
}
|
||||
|
||||
func (fs *loggingFS) Open(name string, opts ...OpenOption) (File, error) {
|
||||
fs.logFn("open: %s", name)
|
||||
f, err := fs.FS.Open(name, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newLoggingFile(f, name, fs.logFn), nil
|
||||
}
|
||||
|
||||
func (fs *loggingFS) Link(oldname, newname string) error {
|
||||
fs.logFn("link: %s -> %s", oldname, newname)
|
||||
return fs.FS.Link(oldname, newname)
|
||||
}
|
||||
|
||||
func (fs *loggingFS) OpenDir(name string) (File, error) {
|
||||
fs.logFn("open-dir: %s", name)
|
||||
f, err := fs.FS.OpenDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newLoggingFile(f, name, fs.logFn), nil
|
||||
}
|
||||
|
||||
func (fs *loggingFS) Rename(oldname, newname string) error {
|
||||
fs.logFn("rename: %s -> %s", oldname, newname)
|
||||
return fs.FS.Rename(oldname, newname)
|
||||
}
|
||||
|
||||
func (fs *loggingFS) ReuseForWrite(oldname, newname string) (File, error) {
|
||||
fs.logFn("reuseForWrite: %s -> %s", oldname, newname)
|
||||
f, err := fs.FS.ReuseForWrite(oldname, newname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newLoggingFile(f, newname, fs.logFn), nil
|
||||
}
|
||||
|
||||
func (fs *loggingFS) MkdirAll(dir string, perm os.FileMode) error {
|
||||
fs.logFn("mkdir-all: %s %#o", dir, perm)
|
||||
return fs.FS.MkdirAll(dir, perm)
|
||||
}
|
||||
|
||||
func (fs *loggingFS) Lock(name string) (io.Closer, error) {
|
||||
fs.logFn("lock: %s", name)
|
||||
return fs.FS.Lock(name)
|
||||
}
|
||||
|
||||
func (fs loggingFS) Remove(name string) error {
|
||||
fs.logFn("remove: %s", name)
|
||||
err := fs.FS.Remove(name)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs loggingFS) RemoveAll(name string) error {
|
||||
fs.logFn("remove-all: %s", name)
|
||||
err := fs.FS.RemoveAll(name)
|
||||
return err
|
||||
}
|
||||
|
||||
type loggingFile struct {
|
||||
File
|
||||
name string
|
||||
logFn LogFn
|
||||
}
|
||||
|
||||
var _ File = (*loggingFile)(nil)
|
||||
|
||||
func newLoggingFile(f File, name string, logFn LogFn) *loggingFile {
|
||||
return &loggingFile{
|
||||
File: f,
|
||||
name: name,
|
||||
logFn: logFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *loggingFile) Close() error {
|
||||
f.logFn("close: %s", f.name)
|
||||
return f.File.Close()
|
||||
}
|
||||
|
||||
func (f *loggingFile) Sync() error {
|
||||
f.logFn("sync: %s", f.name)
|
||||
return f.File.Sync()
|
||||
}
|
||||
|
||||
func (f *loggingFile) SyncData() error {
|
||||
f.logFn("sync-data: %s", f.name)
|
||||
return f.File.SyncData()
|
||||
}
|
||||
|
||||
func (f *loggingFile) SyncTo(length int64) (fullSync bool, err error) {
|
||||
f.logFn("sync-to(%d): %s", length, f.name)
|
||||
return f.File.SyncTo(length)
|
||||
}
|
||||
|
||||
func (f *loggingFile) ReadAt(p []byte, offset int64) (int, error) {
|
||||
f.logFn("read-at(%d, %d): %s", offset, len(p), f.name)
|
||||
return f.File.ReadAt(p, offset)
|
||||
}
|
||||
|
||||
func (f *loggingFile) Prefetch(offset int64, length int64) error {
|
||||
f.logFn("prefetch(%d, %d): %s", offset, length, f.name)
|
||||
return f.File.Prefetch(offset, length)
|
||||
}
|
||||
61
vendor/github.com/cockroachdb/pebble/vfs/mem_fs.go
generated
vendored
61
vendor/github.com/cockroachdb/pebble/vfs/mem_fs.go
generated
vendored
@@ -36,22 +36,23 @@ func NewMem() *MemFS {
|
||||
// at which point they are discarded and no longer visible.
|
||||
//
|
||||
// Expected usage:
|
||||
// strictFS := NewStrictMem()
|
||||
// db := Open(..., &Options{FS: strictFS})
|
||||
// // Do and commit various operations.
|
||||
// ...
|
||||
// // Prevent any more changes to finalized state.
|
||||
// strictFS.SetIgnoreSyncs(true)
|
||||
// // This will finish any ongoing background flushes, compactions but none of these writes will
|
||||
// // be finalized since syncs are being ignored.
|
||||
// db.Close()
|
||||
// // Discard unsynced state.
|
||||
// strictFS.ResetToSyncedState()
|
||||
// // Allow changes to finalized state.
|
||||
// strictFS.SetIgnoreSyncs(false)
|
||||
// // Open the DB. This DB should have the same state as if the earlier strictFS operations and
|
||||
// // db.Close() were not called.
|
||||
// db := Open(..., &Options{FS: strictFS})
|
||||
//
|
||||
// strictFS := NewStrictMem()
|
||||
// db := Open(..., &Options{FS: strictFS})
|
||||
// // Do and commit various operations.
|
||||
// ...
|
||||
// // Prevent any more changes to finalized state.
|
||||
// strictFS.SetIgnoreSyncs(true)
|
||||
// // This will finish any ongoing background flushes, compactions but none of these writes will
|
||||
// // be finalized since syncs are being ignored.
|
||||
// db.Close()
|
||||
// // Discard unsynced state.
|
||||
// strictFS.ResetToSyncedState()
|
||||
// // Allow changes to finalized state.
|
||||
// strictFS.SetIgnoreSyncs(false)
|
||||
// // Open the DB. This DB should have the same state as if the earlier strictFS operations and
|
||||
// // db.Close() were not called.
|
||||
// db := Open(..., &Options{FS: strictFS})
|
||||
func NewStrictMem() *MemFS {
|
||||
return &MemFS{
|
||||
root: newRootMemNode(),
|
||||
@@ -141,6 +142,7 @@ func (y *MemFS) ResetToSyncedState() {
|
||||
// - "/", "foo", false
|
||||
// - "/foo/", "bar", false
|
||||
// - "/foo/bar/", "x", true
|
||||
//
|
||||
// Similarly, walking "/y/z/", with a trailing slash, will result in 3 calls to f:
|
||||
// - "/", "y", false
|
||||
// - "/y/", "z", false
|
||||
@@ -624,6 +626,8 @@ type memFile struct {
|
||||
read, write bool
|
||||
}
|
||||
|
||||
var _ File = (*memFile)(nil)
|
||||
|
||||
func (f *memFile) Close() error {
|
||||
if n := atomic.AddInt32(&f.n.refs, -1); n < 0 {
|
||||
panic(fmt.Sprintf("pebble: close of unopened file: %d", n))
|
||||
@@ -661,7 +665,11 @@ func (f *memFile) ReadAt(p []byte, off int64) (int, error) {
|
||||
if off >= int64(len(f.n.mu.data)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return copy(p, f.n.mu.data[off:]), nil
|
||||
n := copy(p, f.n.mu.data[off:])
|
||||
if n < len(p) {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *memFile) Write(p []byte) (int, error) {
|
||||
@@ -694,6 +702,9 @@ func (f *memFile) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (f *memFile) Prefetch(offset int64, length int64) error { return nil }
|
||||
func (f *memFile) Preallocate(offset, length int64) error { return nil }
|
||||
|
||||
func (f *memFile) Stat() (os.FileInfo, error) {
|
||||
return f.n, nil
|
||||
}
|
||||
@@ -719,6 +730,22 @@ func (f *memFile) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *memFile) SyncData() error {
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
func (f *memFile) SyncTo(length int64) (fullSync bool, err error) {
|
||||
// NB: This SyncTo implementation lies, with its return values claiming it
|
||||
// synced the data up to `length`. When fullSync=false, SyncTo provides no
|
||||
// durability guarantees, so this can help surface bugs where we improperly
|
||||
// rely on SyncTo providing durability.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (f *memFile) Fd() uintptr {
|
||||
return InvalidFd
|
||||
}
|
||||
|
||||
// Flush is a no-op and present only to prevent buffering at higher levels
|
||||
// (e.g. it prevents sstable.Writer from using a bufio.Writer).
|
||||
func (f *memFile) Flush() error {
|
||||
|
||||
25
vendor/github.com/cockroachdb/pebble/vfs/preallocate_generic.go
generated
vendored
25
vendor/github.com/cockroachdb/pebble/vfs/preallocate_generic.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package vfs
|
||||
|
||||
func preallocExtend(fd uintptr, offset, length int64) error {
|
||||
// It is ok for correctness to no-op file preallocation. WAL recycling is the
|
||||
// more important mechanism for WAL sync performance and it doesn't rely on
|
||||
// fallocate or posix_fallocate in order to be effective.
|
||||
return nil
|
||||
}
|
||||
28
vendor/github.com/cockroachdb/pebble/vfs/preallocate_linux.go
generated
vendored
28
vendor/github.com/cockroachdb/pebble/vfs/preallocate_linux.go
generated
vendored
@@ -1,28 +0,0 @@
|
||||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func preallocExtend(fd uintptr, offset, length int64) error {
|
||||
return syscall.Fallocate(int(fd), unix.FALLOC_FL_KEEP_SIZE, offset, length)
|
||||
}
|
||||
16
vendor/github.com/cockroachdb/pebble/vfs/prefetch_generic.go
generated
vendored
16
vendor/github.com/cockroachdb/pebble/vfs/prefetch_generic.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package vfs
|
||||
|
||||
// Prefetch signals the OS (on supported platforms) to fetch the next size
|
||||
// bytes in file (as returned by os.File.Fd()) after offset into cache. Any
|
||||
// subsequent reads in that range will not issue disk IO.
|
||||
func Prefetch(file uintptr, offset uint64, size uint64) error {
|
||||
// No-op.
|
||||
return nil
|
||||
}
|
||||
18
vendor/github.com/cockroachdb/pebble/vfs/prefetch_linux.go
generated
vendored
18
vendor/github.com/cockroachdb/pebble/vfs/prefetch_linux.go
generated
vendored
@@ -1,18 +0,0 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vfs
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Prefetch signals the OS (on supported platforms) to fetch the next size
|
||||
// bytes in file (as returned by os.File.Fd()) after offset into cache. Any
|
||||
// subsequent reads in that range will not issue disk IO.
|
||||
func Prefetch(file uintptr, offset uint64, size uint64) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_READAHEAD, uintptr(file), uintptr(offset), uintptr(size))
|
||||
return err
|
||||
}
|
||||
119
vendor/github.com/cockroachdb/pebble/vfs/syncing_file.go
generated
vendored
119
vendor/github.com/cockroachdb/pebble/vfs/syncing_file.go
generated
vendored
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
// SyncingFileOptions holds the options for a syncingFile.
|
||||
type SyncingFileOptions struct {
|
||||
// NoSyncOnClose elides the automatic Sync during Close if it's not possible
|
||||
// to sync the remainder of the file in a non-blocking way.
|
||||
NoSyncOnClose bool
|
||||
BytesPerSync int
|
||||
PreallocateSize int
|
||||
@@ -19,9 +21,8 @@ type SyncingFileOptions struct {
|
||||
|
||||
type syncingFile struct {
|
||||
File
|
||||
// fd can be InvalidFd if the underlying File does not support it.
|
||||
fd uintptr
|
||||
useSyncRange bool
|
||||
closing bool
|
||||
noSyncOnClose bool
|
||||
bytesPerSync int64
|
||||
preallocateSize int64
|
||||
@@ -36,9 +37,6 @@ type syncingFile struct {
|
||||
syncOffset int64
|
||||
}
|
||||
preallocatedBlocks int64
|
||||
syncData func() error
|
||||
syncTo func(offset int64) error
|
||||
timeDiskOp func(op func())
|
||||
}
|
||||
|
||||
// NewSyncingFile wraps a writable file and ensures that data is synced
|
||||
@@ -49,6 +47,7 @@ type syncingFile struct {
|
||||
func NewSyncingFile(f File, opts SyncingFileOptions) File {
|
||||
s := &syncingFile{
|
||||
File: f,
|
||||
fd: f.Fd(),
|
||||
noSyncOnClose: bool(opts.NoSyncOnClose),
|
||||
bytesPerSync: int64(opts.BytesPerSync),
|
||||
preallocateSize: int64(opts.PreallocateSize),
|
||||
@@ -56,30 +55,7 @@ func NewSyncingFile(f File, opts SyncingFileOptions) File {
|
||||
// Ensure a file that is opened and then closed will be synced, even if no
|
||||
// data has been written to it.
|
||||
s.atomic.syncOffset = -1
|
||||
|
||||
type fd interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
if d, ok := f.(fd); ok {
|
||||
s.fd = d.Fd()
|
||||
}
|
||||
type dhChecker interface {
|
||||
timeDiskOp(op func())
|
||||
}
|
||||
if d, ok := f.(dhChecker); ok {
|
||||
s.timeDiskOp = d.timeDiskOp
|
||||
} else {
|
||||
s.timeDiskOp = func(op func()) {
|
||||
op()
|
||||
}
|
||||
}
|
||||
|
||||
s.init()
|
||||
|
||||
if s.syncData == nil {
|
||||
s.syncData = s.File.Sync
|
||||
}
|
||||
return WithFd(f, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// NB: syncingFile.Write is unsafe for concurrent use!
|
||||
@@ -100,7 +76,7 @@ func (f *syncingFile) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (f *syncingFile) preallocate(offset int64) error {
|
||||
if f.fd == 0 || f.preallocateSize == 0 {
|
||||
if f.fd == InvalidFd || f.preallocateSize == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -112,7 +88,7 @@ func (f *syncingFile) preallocate(offset int64) error {
|
||||
length := f.preallocateSize * (newPreallocatedBlocks - f.preallocatedBlocks)
|
||||
offset = f.preallocateSize * f.preallocatedBlocks
|
||||
f.preallocatedBlocks = newPreallocatedBlocks
|
||||
return preallocExtend(f.fd, offset, length)
|
||||
return f.Preallocate(offset, length)
|
||||
}
|
||||
|
||||
func (f *syncingFile) ratchetSyncOffset(offset int64) {
|
||||
@@ -131,9 +107,10 @@ func (f *syncingFile) Sync() error {
|
||||
// We update syncOffset (atomically) in order to avoid spurious syncs in
|
||||
// maybeSync. Note that even if syncOffset is larger than the current file
|
||||
// offset, we still need to call the underlying file's sync for persistence
|
||||
// guarantees (which are not provided by sync_file_range).
|
||||
// guarantees which are not provided by SyncTo (or by sync_file_range on
|
||||
// Linux).
|
||||
f.ratchetSyncOffset(atomic.LoadInt64(&f.atomic.offset))
|
||||
return f.syncData()
|
||||
return f.SyncData()
|
||||
}
|
||||
|
||||
func (f *syncingFile) maybeSync() error {
|
||||
@@ -163,30 +140,78 @@ func (f *syncingFile) maybeSync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fd == 0 {
|
||||
if f.fd == InvalidFd {
|
||||
return errors.WithStack(f.Sync())
|
||||
}
|
||||
|
||||
// Note that syncTo will always be called with an offset < atomic.offset. The
|
||||
// syncTo implementation may choose to sync the entire file (i.e. on OSes
|
||||
// which do not support syncing a portion of the file). The syncTo
|
||||
// implementation must call ratchetSyncOffset with as much of the file as it
|
||||
// has synced.
|
||||
return errors.WithStack(f.syncTo(syncToOffset))
|
||||
// Note that SyncTo will always be called with an offset < atomic.offset.
|
||||
// The SyncTo implementation may choose to sync the entire file (i.e. on
|
||||
// OSes which do not support syncing a portion of the file).
|
||||
fullSync, err := f.SyncTo(syncToOffset)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if fullSync {
|
||||
f.ratchetSyncOffset(offset)
|
||||
} else {
|
||||
f.ratchetSyncOffset(syncToOffset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *syncingFile) Close() error {
|
||||
// Sync any data that has been written but not yet synced unless the file
|
||||
// has noSyncOnClose option explicitly set.
|
||||
// Note that if SyncFileRange was used, atomic.syncOffset will be less than
|
||||
// atomic.offset. See syncingFile.syncToRange.
|
||||
f.closing = true
|
||||
if !f.noSyncOnClose || f.useSyncRange {
|
||||
if atomic.LoadInt64(&f.atomic.offset) > atomic.LoadInt64(&f.atomic.syncOffset) {
|
||||
if err := f.Sync(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
//
|
||||
// NB: If the file is capable of non-durability-guarantee SyncTos, and the
|
||||
// caller has not called Sync since the last write, syncOffset is guaranteed
|
||||
// to be less than atomic.offset. This ensures we fall into the below
|
||||
// conditional and perform a full sync to durably persist the file.
|
||||
if off := atomic.LoadInt64(&f.atomic.offset); off > atomic.LoadInt64(&f.atomic.syncOffset) {
|
||||
// There's still remaining dirty data.
|
||||
|
||||
if f.noSyncOnClose {
|
||||
// If NoSyncOnClose is set, only perform a SyncTo. On linux, SyncTo
|
||||
// translates to a non-blocking `sync_file_range` call which
|
||||
// provides no persistence guarantee. Since it's non-blocking,
|
||||
// there's no latency hit of a blocking sync call, but we still
|
||||
// ensure we're not allowing significant dirty data to accumulate.
|
||||
if _, err := f.File.SyncTo(off); err != nil {
|
||||
return err
|
||||
}
|
||||
f.ratchetSyncOffset(off)
|
||||
} else if err := f.Sync(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return errors.WithStack(f.File.Close())
|
||||
}
|
||||
|
||||
// NewSyncingFS wraps a vfs.FS with one that wraps newly created files with
|
||||
// vfs.NewSyncingFile.
|
||||
func NewSyncingFS(fs FS, syncOpts SyncingFileOptions) FS {
|
||||
return &syncingFS{
|
||||
FS: fs,
|
||||
syncOpts: syncOpts,
|
||||
}
|
||||
}
|
||||
|
||||
type syncingFS struct {
|
||||
FS
|
||||
syncOpts SyncingFileOptions
|
||||
}
|
||||
|
||||
var _ FS = (*syncingFS)(nil)
|
||||
|
||||
func (fs *syncingFS) Create(name string) (File, error) {
|
||||
f, err := fs.FS.Create(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSyncingFile(f, fs.syncOpts), nil
|
||||
}
|
||||
|
||||
func (fs *syncingFS) ReuseForWrite(oldname, newname string) (File, error) {
|
||||
// TODO(radu): implement this if needed.
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
16
vendor/github.com/cockroachdb/pebble/vfs/syncing_file_generic.go
generated
vendored
16
vendor/github.com/cockroachdb/pebble/vfs/syncing_file_generic.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//go:build !linux || arm
|
||||
// +build !linux arm
|
||||
|
||||
package vfs
|
||||
|
||||
func (f *syncingFile) init() {
|
||||
f.syncTo = f.syncToGeneric
|
||||
}
|
||||
|
||||
func (f *syncingFile) syncToGeneric(_ int64) error {
|
||||
return f.Sync()
|
||||
}
|
||||
105
vendor/github.com/cockroachdb/pebble/vfs/syncing_file_linux.go
generated
vendored
105
vendor/github.com/cockroachdb/pebble/vfs/syncing_file_linux.go
generated
vendored
@@ -1,105 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//go:build linux && !arm
|
||||
// +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
|
||||
}
|
||||
|
||||
// Allowlist which filesystems we allow using sync_file_range with as some
|
||||
// filesystems treat that syscall as a noop (notably ZFS). A allowlist is
|
||||
// used instead of a denylist 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.timeDiskOp(func() {
|
||||
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()
|
||||
}
|
||||
var err error
|
||||
f.timeDiskOp(func() {
|
||||
err = syscall.Fdatasync(int(f.fd))
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *syncingFile) syncToFdatasync(_ int64) error {
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
func (f *syncingFile) syncToRange(offset int64) error {
|
||||
const (
|
||||
waitBefore = 0x1
|
||||
write = 0x2
|
||||
// waitAfter = 0x4
|
||||
)
|
||||
|
||||
// The flags for the sync_file_range system call. Unless the file has
|
||||
// noSyncOnClose explicitly set and it is being closed, the waitBefore
|
||||
// flag will be set which may block the call.
|
||||
flags := write
|
||||
if !f.noSyncOnClose || !f.closing {
|
||||
flags |= waitBefore
|
||||
}
|
||||
|
||||
// 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.
|
||||
var err error
|
||||
f.timeDiskOp(func() {
|
||||
err = syscall.SyncFileRange(int(f.fd), 0, offset, flags)
|
||||
})
|
||||
return err
|
||||
}
|
||||
92
vendor/github.com/cockroachdb/pebble/vfs/vfs.go
generated
vendored
92
vendor/github.com/cockroachdb/pebble/vfs/vfs.go
generated
vendored
@@ -29,10 +29,53 @@ type File interface {
|
||||
// method *is* allowed to modify the slice passed in, whether temporarily
|
||||
// or permanently. Callers of Write() need to take this into account.
|
||||
io.Writer
|
||||
|
||||
// Preallocate optionally preallocates storage for `length` at `offset`
|
||||
// within the file. Implementations may choose to do nothing.
|
||||
Preallocate(offset, length int64) error
|
||||
Stat() (os.FileInfo, error)
|
||||
Sync() error
|
||||
|
||||
// SyncTo requests that a prefix of the file's data be synced to stable
|
||||
// storage. The caller passes provides a `length`, indicating how many bytes
|
||||
// to sync from the beginning of the file. SyncTo is a no-op for
|
||||
// directories, and therefore always returns false.
|
||||
//
|
||||
// SyncTo returns a fullSync return value, indicating one of two possible
|
||||
// outcomes.
|
||||
//
|
||||
// If fullSync is false, the first `length` bytes of the file was queued to
|
||||
// be synced to stable storage. The syncing of the file prefix may happen
|
||||
// asynchronously. No persistence guarantee is provided.
|
||||
//
|
||||
// If fullSync is true, the entirety of the file's contents were
|
||||
// synchronously synced to stable storage, and a persistence guarantee is
|
||||
// provided. In this outcome, any modified metadata for the file is not
|
||||
// guaranteed to be synced unless that metadata is needed in order to allow
|
||||
// a subsequent data retrieval to be correctly handled.
|
||||
SyncTo(length int64) (fullSync bool, err error)
|
||||
|
||||
// SyncData requires that all written data be persisted. File metadata is
|
||||
// not required to be synced. Unsophisticated implementations may call Sync.
|
||||
SyncData() error
|
||||
|
||||
// Prefetch signals the OS (on supported platforms) to fetch the next length
|
||||
// bytes in file (as returned by os.File.Fd()) after offset into cache. Any
|
||||
// subsequent reads in that range will not issue disk IO.
|
||||
Prefetch(offset int64, length int64) error
|
||||
|
||||
// Fd returns the raw file descriptor when a File is backed by an *os.File.
|
||||
// It can be used for specific functionality like Prefetch.
|
||||
// Returns InvalidFd if not supported.
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// InvalidFd is a special value returned by File.Fd() when the file is not
|
||||
// backed by an OS descriptor.
|
||||
// Note: the special value is consistent with what os.File implementation
|
||||
// returns on a nil receiver.
|
||||
const InvalidFd uintptr = ^(uintptr(0))
|
||||
|
||||
// OpenOption provide an interface to do work on file handles in the Open()
|
||||
// call.
|
||||
type OpenOption interface {
|
||||
@@ -144,10 +187,18 @@ var Default FS = defaultFS{}
|
||||
|
||||
type defaultFS struct{}
|
||||
|
||||
// wrapOSFile takes a standard library OS file and returns a vfs.File. f may be
|
||||
// nil, in which case wrapOSFile must not panic. In such cases, it's okay if the
|
||||
// returned vfs.File may panic if used.
|
||||
func wrapOSFile(f *os.File) File {
|
||||
// See the implementations in default_{linux,unix,windows}.go.
|
||||
return wrapOSFileImpl(f)
|
||||
}
|
||||
|
||||
func (defaultFS) Create(name string) (File, error) {
|
||||
const openFlags = os.O_RDWR | os.O_CREATE | os.O_EXCL | syscall.O_CLOEXEC
|
||||
|
||||
f, err := os.OpenFile(name, openFlags, 0666)
|
||||
osFile, err := os.OpenFile(name, openFlags, 0666)
|
||||
// If the file already exists, remove it and try again.
|
||||
//
|
||||
// NB: We choose to remove the file instead of truncating it, despite the
|
||||
@@ -158,11 +209,11 @@ func (defaultFS) Create(name string) (File, error) {
|
||||
// attempting to create the a file at the same path.
|
||||
for oserror.IsExist(err) {
|
||||
if removeErr := os.Remove(name); removeErr != nil && !oserror.IsNotExist(removeErr) {
|
||||
return f, errors.WithStack(removeErr)
|
||||
return wrapOSFile(osFile), errors.WithStack(removeErr)
|
||||
}
|
||||
f, err = os.OpenFile(name, openFlags, 0666)
|
||||
osFile, err = os.OpenFile(name, openFlags, 0666)
|
||||
}
|
||||
return f, errors.WithStack(err)
|
||||
return wrapOSFile(osFile), errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (defaultFS) Link(oldname, newname string) error {
|
||||
@@ -170,10 +221,11 @@ func (defaultFS) Link(oldname, newname string) error {
|
||||
}
|
||||
|
||||
func (defaultFS) Open(name string, opts ...OpenOption) (File, error) {
|
||||
file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_CLOEXEC, 0)
|
||||
osFile, err := os.OpenFile(name, os.O_RDONLY|syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
file := wrapOSFile(osFile)
|
||||
for _, opt := range opts {
|
||||
opt.Apply(file)
|
||||
}
|
||||
@@ -197,7 +249,7 @@ func (fs defaultFS) ReuseForWrite(oldname, newname string) (File, error) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
f, err := os.OpenFile(newname, os.O_RDWR|os.O_CREATE|syscall.O_CLOEXEC, 0666)
|
||||
return f, errors.WithStack(err)
|
||||
return wrapOSFile(f), errors.WithStack(err)
|
||||
}
|
||||
|
||||
func (defaultFS) MkdirAll(dir string, perm os.FileMode) error {
|
||||
@@ -240,11 +292,8 @@ var RandomReadsOption OpenOption = &randomReadsOption{}
|
||||
|
||||
// Apply implements the OpenOption interface.
|
||||
func (randomReadsOption) Apply(f File) {
|
||||
type fd interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
if fdFile, ok := f.(fd); ok {
|
||||
_ = fadviseRandom(fdFile.Fd())
|
||||
if fd := f.Fd(); fd != InvalidFd {
|
||||
_ = fadviseRandom(fd)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,24 +306,27 @@ var SequentialReadsOption OpenOption = &sequentialReadsOption{}
|
||||
|
||||
// Apply implements the OpenOption interface.
|
||||
func (sequentialReadsOption) Apply(f File) {
|
||||
type fd interface {
|
||||
Fd() uintptr
|
||||
}
|
||||
if fdFile, ok := f.(fd); ok {
|
||||
_ = fadviseSequential(fdFile.Fd())
|
||||
if fd := f.Fd(); fd != InvalidFd {
|
||||
_ = fadviseSequential(fd)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy copies the contents of oldname to newname. If newname exists, it will
|
||||
// be overwritten.
|
||||
func Copy(fs FS, oldname, newname string) error {
|
||||
src, err := fs.Open(oldname)
|
||||
return CopyAcrossFS(fs, oldname, fs, newname)
|
||||
}
|
||||
|
||||
// CopyAcrossFS copies the contents of oldname on srcFS to newname dstFS. If
|
||||
// newname exists, it will be overwritten.
|
||||
func CopyAcrossFS(srcFS FS, oldname string, dstFS FS, newname string) error {
|
||||
src, err := srcFS.Open(oldname, SequentialReadsOption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := fs.Create(newname)
|
||||
dst, err := dstFS.Create(newname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,7 +341,7 @@ func Copy(fs FS, oldname, newname string) error {
|
||||
// LimitedCopy copies up to maxBytes from oldname to newname. If newname
|
||||
// exists, it will be overwritten.
|
||||
func LimitedCopy(fs FS, oldname, newname string, maxBytes int64) error {
|
||||
src, err := fs.Open(oldname)
|
||||
src, err := fs.Open(oldname, SequentialReadsOption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -309,7 +361,7 @@ func LimitedCopy(fs FS, oldname, newname string, maxBytes int64) error {
|
||||
|
||||
// LinkOrCopy creates newname as a hard link to the oldname file. If creating
|
||||
// the hard link fails, LinkOrCopy falls back to copying the file (which may
|
||||
// also fail if newname doesn't exist or oldname already exists).
|
||||
// also fail if oldname doesn't exist or newname already exists).
|
||||
func LinkOrCopy(fs FS, oldname, newname string) error {
|
||||
err := fs.Link(oldname, newname)
|
||||
if err == nil {
|
||||
|
||||
Reference in New Issue
Block a user