mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #4775 from heyitsanthony/wal-locks
fileutil, wal: refactor file locking
This commit is contained in:
commit
6aa17f0c76
@ -14,16 +14,13 @@
|
||||
|
||||
package fileutil
|
||||
|
||||
type Lock interface {
|
||||
// Name returns the name of the file.
|
||||
Name() string
|
||||
// TryLock acquires exclusivity on the lock without blocking.
|
||||
TryLock() error
|
||||
// Lock acquires exclusivity on the lock.
|
||||
Lock() error
|
||||
// Unlock unlocks the lock.
|
||||
Unlock() error
|
||||
// Destroy should be called after Unlock to clean up
|
||||
// the resources.
|
||||
Destroy() error
|
||||
}
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("fileutil: file already locked")
|
||||
)
|
||||
|
||||
type LockedFile struct{ *os.File }
|
||||
|
@ -15,65 +15,31 @@
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type lock struct {
|
||||
fname string
|
||||
file *os.File
|
||||
func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
if err := os.Chmod(path, syscall.DMEXCL|0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, ErrLocked
|
||||
}
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
|
||||
func (l *lock) Name() string {
|
||||
return l.fname
|
||||
}
|
||||
|
||||
func (l *lock) TryLock() error {
|
||||
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
|
||||
if err != nil {
|
||||
return err
|
||||
func LockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
if err := os.Chmod(path, syscall.DMEXCL|0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Open(l.fname)
|
||||
if err != nil {
|
||||
return ErrLocked
|
||||
}
|
||||
|
||||
l.file = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lock) Lock() error {
|
||||
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
f, err := os.Open(l.fname)
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err == nil {
|
||||
l.file = f
|
||||
return nil
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lock) Unlock() error {
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
func (l *lock) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLock(file string) (Lock, error) {
|
||||
l := &lock{fname: file}
|
||||
return l, nil
|
||||
}
|
||||
|
@ -17,25 +17,11 @@
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
func (l *lock) TryLock() error {
|
||||
func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
@ -43,45 +29,34 @@ func (l *lock) TryLock() error {
|
||||
lock.Type = syscall.F_WRLCK
|
||||
lock.Whence = 0
|
||||
lock.Pid = 0
|
||||
err := syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
|
||||
if err != nil && err == syscall.EAGAIN {
|
||||
return ErrLocked
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *lock) Lock() error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Type = syscall.F_WRLCK
|
||||
lock.Whence = 0
|
||||
lock.Pid = 0
|
||||
return syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
|
||||
}
|
||||
|
||||
func (l *lock) Unlock() error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Type = syscall.F_UNLCK
|
||||
lock.Whence = 0
|
||||
err := syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
|
||||
if err != nil && err == syscall.EAGAIN {
|
||||
return ErrLocked
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *lock) Destroy() error {
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
func NewLock(file string) (Lock, error) {
|
||||
f, err := os.OpenFile(file, os.O_WRONLY, 0600)
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &lock{int(f.Fd()), f}
|
||||
return l, nil
|
||||
if err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock); err != nil {
|
||||
f.Close()
|
||||
if err == syscall.EAGAIN {
|
||||
err = ErrLocked
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
|
||||
func LockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Pid = 0
|
||||
lock.Type = syscall.F_WRLCK
|
||||
lock.Whence = 0
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = syscall.FcntlFlock(f.Fd(), syscall.F_SETLKW, &lock); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
|
@ -35,44 +35,38 @@ func TestLockAndUnlock(t *testing.T) {
|
||||
}()
|
||||
|
||||
// lock the file
|
||||
l, err := NewLock(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Destroy()
|
||||
err = l.Lock()
|
||||
l, err := LockFile(f.Name(), os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// try lock a locked file
|
||||
dupl, err := NewLock(f.Name())
|
||||
if err != nil {
|
||||
if _, err = TryLockFile(f.Name(), os.O_WRONLY, 0600); err != ErrLocked {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = dupl.TryLock()
|
||||
if err != ErrLocked {
|
||||
t.Errorf("err = %v, want %v", err, ErrLocked)
|
||||
}
|
||||
|
||||
// unlock the file
|
||||
err = l.Unlock()
|
||||
if err != nil {
|
||||
if err = l.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// try lock the unlocked file
|
||||
err = dupl.TryLock()
|
||||
dupl, err := TryLockFile(f.Name(), os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want %v", err, nil)
|
||||
}
|
||||
defer dupl.Destroy()
|
||||
|
||||
// blocking on locked file
|
||||
locked := make(chan struct{}, 1)
|
||||
go func() {
|
||||
l.Lock()
|
||||
bl, blerr := LockFile(f.Name(), os.O_WRONLY, 0600)
|
||||
if blerr != nil {
|
||||
t.Fatal(blerr)
|
||||
}
|
||||
locked <- struct{}{}
|
||||
if blerr = bl.Close(); blerr != nil {
|
||||
t.Fatal(blerr)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
@ -82,8 +76,7 @@ func TestLockAndUnlock(t *testing.T) {
|
||||
}
|
||||
|
||||
// unlock
|
||||
err = dupl.Unlock()
|
||||
if err != nil {
|
||||
if err = dupl.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -17,49 +17,33 @@
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
func (l *lock) TryLock() error {
|
||||
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil && err == syscall.EWOULDBLOCK {
|
||||
return ErrLocked
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *lock) Lock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_EX)
|
||||
}
|
||||
|
||||
func (l *lock) Unlock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_UN)
|
||||
}
|
||||
|
||||
func (l *lock) Destroy() error {
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
func NewLock(file string) (Lock, error) {
|
||||
f, err := os.Open(file)
|
||||
func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &lock{int(f.Fd()), f}
|
||||
return l, nil
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
|
||||
f.Close()
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
err = ErrLocked
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
|
||||
func LockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &LockedFile{f}, err
|
||||
}
|
||||
|
@ -16,45 +16,17 @@
|
||||
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
import "os"
|
||||
|
||||
var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
func TryLockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
return LockFile(path, flag, perm)
|
||||
}
|
||||
|
||||
func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
func (l *lock) TryLock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lock) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lock) Unlock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lock) Destroy() error {
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
func NewLock(file string) (Lock, error) {
|
||||
f, err := os.Open(file)
|
||||
func LockFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
|
||||
// TODO make this actually work
|
||||
f, err := os.OpenFile(path, flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &lock{int(f.Fd()), f}
|
||||
return l, nil
|
||||
return &LockedFile{f}, nil
|
||||
}
|
||||
|
@ -40,32 +40,19 @@ func PurgeFile(dirname string, suffix string, max uint, interval time.Duration,
|
||||
sort.Strings(newfnames)
|
||||
for len(newfnames) > int(max) {
|
||||
f := path.Join(dirname, newfnames[0])
|
||||
l, err := NewLock(f)
|
||||
if err != nil {
|
||||
errC <- err
|
||||
return
|
||||
}
|
||||
err = l.TryLock()
|
||||
l, err := TryLockFile(f, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = os.Remove(f)
|
||||
if err != nil {
|
||||
if err = os.Remove(f); err != nil {
|
||||
errC <- err
|
||||
return
|
||||
}
|
||||
err = l.Unlock()
|
||||
if err != nil {
|
||||
if err = l.Close(); err != nil {
|
||||
plog.Errorf("error unlocking %s when purging file (%v)", l.Name(), err)
|
||||
errC <- err
|
||||
return
|
||||
}
|
||||
err = l.Destroy()
|
||||
if err != nil {
|
||||
plog.Errorf("error destroying lock %s when purging file (%v)", l.Name(), err)
|
||||
errC <- err
|
||||
return
|
||||
}
|
||||
plog.Infof("purged file %s successfully", f)
|
||||
newfnames = newfnames[1:]
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func TestPurgeFile(t *testing.T) {
|
||||
close(stop)
|
||||
}
|
||||
|
||||
func TestPurgeFileHoldingLock(t *testing.T) {
|
||||
func TestPurgeFileHoldingLockFile(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "purgefile")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -95,8 +95,8 @@ func TestPurgeFileHoldingLock(t *testing.T) {
|
||||
}
|
||||
|
||||
// create a purge barrier at 5
|
||||
l, err := NewLock(path.Join(dir, fmt.Sprintf("%d.test", 5)))
|
||||
err = l.Lock()
|
||||
p := path.Join(dir, fmt.Sprintf("%d.test", 5))
|
||||
l, err := LockFile(p, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -127,12 +127,7 @@ func TestPurgeFileHoldingLock(t *testing.T) {
|
||||
}
|
||||
|
||||
// remove the purge barrier
|
||||
err = l.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Destroy()
|
||||
if err != nil {
|
||||
if err = l.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -31,14 +31,12 @@ type decoder struct {
|
||||
mu sync.Mutex
|
||||
br *bufio.Reader
|
||||
|
||||
c io.Closer
|
||||
crc hash.Hash32
|
||||
}
|
||||
|
||||
func newDecoder(rc io.ReadCloser) *decoder {
|
||||
func newDecoder(r io.Reader) *decoder {
|
||||
return &decoder{
|
||||
br: bufio.NewReader(rc),
|
||||
c: rc,
|
||||
br: bufio.NewReader(r),
|
||||
crc: crc.New(0, crcTable),
|
||||
}
|
||||
}
|
||||
@ -80,10 +78,6 @@ func (d *decoder) lastCRC() uint32 {
|
||||
return d.crc.Sum32()
|
||||
}
|
||||
|
||||
func (d *decoder) close() error {
|
||||
return d.c.Close()
|
||||
}
|
||||
|
||||
func mustUnmarshalEntry(d []byte) raftpb.Entry {
|
||||
var e raftpb.Entry
|
||||
pbutil.MustUnmarshal(&e, d)
|
||||
|
@ -36,7 +36,6 @@ func Repair(dirpath string) bool {
|
||||
rec := &walpb.Record{}
|
||||
|
||||
decoder := newDecoder(f)
|
||||
defer decoder.close()
|
||||
for {
|
||||
err := decoder.decode(rec)
|
||||
switch err {
|
||||
|
192
wal/wal.go
192
wal/wal.go
@ -70,16 +70,15 @@ type WAL struct {
|
||||
metadata []byte // metadata recorded at the head of each WAL
|
||||
state raftpb.HardState // hardstate recorded at the head of WAL
|
||||
|
||||
start walpb.Snapshot // snapshot to start reading
|
||||
decoder *decoder // decoder to decode records
|
||||
start walpb.Snapshot // snapshot to start reading
|
||||
decoder *decoder // decoder to decode records
|
||||
readClose io.Closer // closer for decode reader
|
||||
|
||||
mu sync.Mutex
|
||||
f *os.File // underlay file opened for appending, sync
|
||||
seq uint64 // sequence of the wal file currently used for writes
|
||||
enti uint64 // index of the last entry saved to the wal
|
||||
encoder *encoder // encoder to encode records
|
||||
|
||||
locks []fileutil.Lock // the file locks the WAL is holding (the name is increasing)
|
||||
locks []*fileutil.LockedFile // the locked files the WAL holds (the name is increasing)
|
||||
}
|
||||
|
||||
// Create creates a WAL ready for appending records. The given metadata is
|
||||
@ -94,26 +93,17 @@ func Create(dirpath string, metadata []byte) (*WAL, error) {
|
||||
}
|
||||
|
||||
p := path.Join(dirpath, walName(0, 0))
|
||||
f, err := os.OpenFile(p, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
|
||||
f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := fileutil.NewLock(f.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = l.Lock(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &WAL{
|
||||
dir: dirpath,
|
||||
metadata: metadata,
|
||||
seq: 0,
|
||||
f: f,
|
||||
encoder: newEncoder(f, 0),
|
||||
}
|
||||
w.locks = append(w.locks, l)
|
||||
w.locks = append(w.locks, f)
|
||||
if err := w.saveCrc(0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -157,60 +147,56 @@ func openAtIndex(dirpath string, snap walpb.Snapshot, write bool) (*WAL, error)
|
||||
return nil, ErrFileNotFound
|
||||
}
|
||||
|
||||
// open the wal files for reading
|
||||
// open the wal files
|
||||
rcs := make([]io.ReadCloser, 0)
|
||||
ls := make([]fileutil.Lock, 0)
|
||||
ls := make([]*fileutil.LockedFile, 0)
|
||||
for _, name := range names[nameIndex:] {
|
||||
f, err := os.Open(path.Join(dirpath, name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := fileutil.NewLock(f.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = l.TryLock()
|
||||
if err != nil {
|
||||
if write {
|
||||
p := path.Join(dirpath, name)
|
||||
if write {
|
||||
l, err := fileutil.TryLockFile(p, os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
MultiReadCloser(rcs...).Close()
|
||||
return nil, err
|
||||
}
|
||||
ls = append(ls, l)
|
||||
rcs = append(rcs, l)
|
||||
} else {
|
||||
rf, err := os.OpenFile(p, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ls = append(ls, nil)
|
||||
rcs = append(rcs, rf)
|
||||
}
|
||||
rcs = append(rcs, f)
|
||||
ls = append(ls, l)
|
||||
}
|
||||
|
||||
rc := MultiReadCloser(rcs...)
|
||||
c := rc
|
||||
if write {
|
||||
// write reuses the file descriptors from read; don't close so
|
||||
// WAL can append without dropping the file lock
|
||||
c = nil
|
||||
}
|
||||
|
||||
// create a WAL ready for reading
|
||||
w := &WAL{
|
||||
dir: dirpath,
|
||||
start: snap,
|
||||
decoder: newDecoder(rc),
|
||||
locks: ls,
|
||||
dir: dirpath,
|
||||
start: snap,
|
||||
decoder: newDecoder(rc),
|
||||
readClose: c,
|
||||
locks: ls,
|
||||
}
|
||||
|
||||
if write {
|
||||
// open the last wal file for appending
|
||||
seq, _, err := parseWalName(names[len(names)-1])
|
||||
if err != nil {
|
||||
if _, _, err := parseWalName(path.Base(w.tail().Name())); err != nil {
|
||||
rc.Close()
|
||||
return nil, err
|
||||
}
|
||||
last := path.Join(dirpath, names[len(names)-1])
|
||||
|
||||
f, err := os.OpenFile(last, os.O_WRONLY|os.O_APPEND, 0)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return nil, err
|
||||
}
|
||||
err = fileutil.Preallocate(f, segmentSizeBytes)
|
||||
if err != nil {
|
||||
if err := fileutil.Preallocate(w.tail().File, segmentSizeBytes); err != nil {
|
||||
rc.Close()
|
||||
plog.Errorf("failed to allocate space when creating new wal file (%v)", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.f = f
|
||||
w.seq = seq
|
||||
}
|
||||
|
||||
return w, nil
|
||||
@ -275,7 +261,7 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
|
||||
}
|
||||
}
|
||||
|
||||
switch w.f {
|
||||
switch w.tail() {
|
||||
case nil:
|
||||
// We do not have to read out all entries in read mode.
|
||||
// The last record maybe a partial written one, so
|
||||
@ -298,17 +284,20 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
|
||||
}
|
||||
|
||||
// close decoder, disable reading
|
||||
w.decoder.close()
|
||||
if w.readClose != nil {
|
||||
w.readClose.Close()
|
||||
w.readClose = nil
|
||||
}
|
||||
w.start = walpb.Snapshot{}
|
||||
|
||||
w.metadata = metadata
|
||||
|
||||
if w.f != nil {
|
||||
if w.tail() != nil {
|
||||
// create encoder (chain crc with the decoder), enable appending
|
||||
w.encoder = newEncoder(w.f, w.decoder.lastCRC())
|
||||
w.decoder = nil
|
||||
w.encoder = newEncoder(w.tail(), w.decoder.lastCRC())
|
||||
lastIndexSaved.Set(float64(w.enti))
|
||||
}
|
||||
w.decoder = nil
|
||||
|
||||
return metadata, state, ents, err
|
||||
}
|
||||
@ -321,23 +310,20 @@ func (w *WAL) cut() error {
|
||||
if err := w.sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fpath := path.Join(w.dir, walName(w.seq+1, w.enti+1))
|
||||
fpath := path.Join(w.dir, walName(w.seq()+1, w.enti+1))
|
||||
ftpath := fpath + ".tmp"
|
||||
|
||||
// create a temp wal file with name sequence + 1, or truncate the existing one
|
||||
ft, err := os.OpenFile(ftpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
newTail, err := fileutil.LockFile(ftpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update writer and save the previous crc
|
||||
w.f = ft
|
||||
w.locks = append(w.locks, newTail)
|
||||
prevCrc := w.encoder.crc.Sum32()
|
||||
w.encoder = newEncoder(w.f, prevCrc)
|
||||
w.encoder = newEncoder(w.tail(), prevCrc)
|
||||
if err = w.saveCrc(prevCrc); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -347,47 +333,28 @@ func (w *WAL) cut() error {
|
||||
if err = w.saveState(&w.state); err != nil {
|
||||
return err
|
||||
}
|
||||
// close temp wal file
|
||||
// atomically move temp wal file to wal file
|
||||
if err = w.sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// atomically move temp wal file to wal file
|
||||
if err = os.Rename(ftpath, fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
newTail.Close()
|
||||
|
||||
// open the wal file and update writer again
|
||||
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
if newTail, err = fileutil.LockFile(fpath, os.O_WRONLY|os.O_APPEND, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileutil.Preallocate(f, segmentSizeBytes); err != nil {
|
||||
w.locks[len(w.locks)-1] = newTail
|
||||
|
||||
prevCrc = w.encoder.crc.Sum32()
|
||||
w.encoder = newEncoder(w.tail(), prevCrc)
|
||||
|
||||
if err = fileutil.Preallocate(w.tail().File, segmentSizeBytes); err != nil {
|
||||
plog.Errorf("failed to allocate space when creating new wal file (%v)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
w.f = f
|
||||
prevCrc = w.encoder.crc.Sum32()
|
||||
w.encoder = newEncoder(w.f, prevCrc)
|
||||
|
||||
// lock the new wal file
|
||||
l, err := fileutil.NewLock(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := l.Lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.locks = append(w.locks, l)
|
||||
|
||||
// increase the wal seq
|
||||
w.seq++
|
||||
|
||||
plog.Infof("segmented wal file %v is created", fpath)
|
||||
return nil
|
||||
}
|
||||
@ -399,7 +366,7 @@ func (w *WAL) sync() error {
|
||||
}
|
||||
}
|
||||
start := time.Now()
|
||||
err := fileutil.Fdatasync(w.f)
|
||||
err := fileutil.Fdatasync(w.tail().File)
|
||||
syncDurations.Observe(float64(time.Since(start)) / float64(time.Second))
|
||||
return err
|
||||
}
|
||||
@ -438,8 +405,10 @@ func (w *WAL) ReleaseLockTo(index uint64) error {
|
||||
}
|
||||
|
||||
for i := 0; i < smaller; i++ {
|
||||
w.locks[i].Unlock()
|
||||
w.locks[i].Destroy()
|
||||
if w.locks[i] == nil {
|
||||
continue
|
||||
}
|
||||
w.locks[i].Close()
|
||||
}
|
||||
w.locks = w.locks[smaller:]
|
||||
|
||||
@ -450,22 +419,17 @@ func (w *WAL) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if w.f != nil {
|
||||
if w.tail() != nil {
|
||||
if err := w.sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, l := range w.locks {
|
||||
err := l.Unlock()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to unlock during closing wal: %s", err)
|
||||
if l == nil {
|
||||
continue
|
||||
}
|
||||
err = l.Destroy()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to destroy lock during closing wal: %s", err)
|
||||
if err := l.Close(); err != nil {
|
||||
plog.Errorf("failed to unlock during closing wal: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -514,7 +478,7 @@ func (w *WAL) Save(st raftpb.HardState, ents []raftpb.Entry) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fstat, err := w.f.Stat()
|
||||
fstat, err := w.tail().Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -524,6 +488,7 @@ func (w *WAL) Save(st raftpb.HardState, ents []raftpb.Entry) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: add a test for this code path when refactoring the tests
|
||||
return w.cut()
|
||||
}
|
||||
@ -549,6 +514,25 @@ func (w *WAL) saveCrc(prevCrc uint32) error {
|
||||
return w.encoder.encode(&walpb.Record{Type: crcType, Crc: prevCrc})
|
||||
}
|
||||
|
||||
func (w *WAL) tail() *fileutil.LockedFile {
|
||||
if len(w.locks) > 0 {
|
||||
return w.locks[len(w.locks)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WAL) seq() uint64 {
|
||||
t := w.tail()
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
seq, _, err := parseWalName(path.Base(t.Name()))
|
||||
if err != nil {
|
||||
plog.Fatalf("bad wal name %s (%v)", t.Name(), err)
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
func mustSync(st, prevst raftpb.HardState, entsnum int) bool {
|
||||
// Persistent state on all servers:
|
||||
// (Updated on stable storage before responding to RPCs)
|
||||
|
@ -38,11 +38,11 @@ func TestNew(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
}
|
||||
if g := path.Base(w.f.Name()); g != walName(0, 0) {
|
||||
if g := path.Base(w.tail().Name()); g != walName(0, 0) {
|
||||
t.Errorf("name = %+v, want %+v", g, walName(0, 0))
|
||||
}
|
||||
defer w.Close()
|
||||
gd, err := ioutil.ReadFile(w.f.Name())
|
||||
gd, err := ioutil.ReadFile(w.tail().Name())
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
}
|
||||
@ -100,11 +100,11 @@ func TestOpenAtIndex(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
}
|
||||
if g := path.Base(w.f.Name()); g != walName(0, 0) {
|
||||
if g := path.Base(w.tail().Name()); g != walName(0, 0) {
|
||||
t.Errorf("name = %+v, want %+v", g, walName(0, 0))
|
||||
}
|
||||
if w.seq != 0 {
|
||||
t.Errorf("seq = %d, want %d", w.seq, 0)
|
||||
if w.seq() != 0 {
|
||||
t.Errorf("seq = %d, want %d", w.seq(), 0)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
@ -119,11 +119,11 @@ func TestOpenAtIndex(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
}
|
||||
if g := path.Base(w.f.Name()); g != wname {
|
||||
if g := path.Base(w.tail().Name()); g != wname {
|
||||
t.Errorf("name = %+v, want %+v", g, wname)
|
||||
}
|
||||
if w.seq != 2 {
|
||||
t.Errorf("seq = %d, want %d", w.seq, 2)
|
||||
if w.seq() != 2 {
|
||||
t.Errorf("seq = %d, want %d", w.seq(), 2)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
@ -160,7 +160,7 @@ func TestCut(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wname := walName(1, 1)
|
||||
if g := path.Base(w.f.Name()); g != wname {
|
||||
if g := path.Base(w.tail().Name()); g != wname {
|
||||
t.Errorf("name = %s, want %s", g, wname)
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ func TestCut(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wname = walName(2, 2)
|
||||
if g := path.Base(w.f.Name()); g != wname {
|
||||
if g := path.Base(w.tail().Name()); g != wname {
|
||||
t.Errorf("name = %s, want %s", g, wname)
|
||||
}
|
||||
|
||||
@ -416,10 +416,10 @@ func TestOpenForRead(t *testing.T) {
|
||||
defer os.RemoveAll(p)
|
||||
// create WAL
|
||||
w, err := Create(p, nil)
|
||||
defer w.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
// make 10 separate files
|
||||
for i := 0; i < 10; i++ {
|
||||
es := []raftpb.Entry{{Index: uint64(i)}}
|
||||
@ -436,10 +436,10 @@ func TestOpenForRead(t *testing.T) {
|
||||
|
||||
// All are available for read
|
||||
w2, err := OpenForRead(p, walpb.Snapshot{})
|
||||
defer w2.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer w2.Close()
|
||||
_, _, ents, err := w2.ReadAll()
|
||||
if err != nil {
|
||||
t.Fatalf("err = %v, want nil", err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user