*: lock the in using files; do not purge locked the wal files
parent
dcf34c0ab4
commit
ea94d19147
|
@ -90,21 +90,6 @@ type Response struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
// Save function saves ents and state to the underlying stable storage.
|
|
||||||
// Save MUST block until st and ents are on stable storage.
|
|
||||||
Save(st raftpb.HardState, ents []raftpb.Entry) error
|
|
||||||
// SaveSnap function saves snapshot to the underlying stable storage.
|
|
||||||
SaveSnap(snap raftpb.Snapshot) error
|
|
||||||
|
|
||||||
// TODO: WAL should be able to control cut itself. After implement self-controlled cut,
|
|
||||||
// remove it in this interface.
|
|
||||||
// Cut cuts out a new wal file for saving new state and entries.
|
|
||||||
Cut() error
|
|
||||||
// Close closes the Storage and performs finalization.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
// Start performs any initialization of the Server necessary for it to
|
// Start performs any initialization of the Server necessary for it to
|
||||||
// begin serving requests. It must be called before Do or Process.
|
// begin serving requests. It must be called before Do or Process.
|
||||||
|
@ -295,15 +280,12 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||||
id: id,
|
id: id,
|
||||||
attributes: Attributes{Name: cfg.Name, ClientURLs: cfg.ClientURLs.StringSlice()},
|
attributes: Attributes{Name: cfg.Name, ClientURLs: cfg.ClientURLs.StringSlice()},
|
||||||
Cluster: cfg.Cluster,
|
Cluster: cfg.Cluster,
|
||||||
storage: struct {
|
storage: NewStorage(w, ss),
|
||||||
*wal.WAL
|
stats: sstats,
|
||||||
*snap.Snapshotter
|
lstats: lstats,
|
||||||
}{w, ss},
|
Ticker: time.Tick(100 * time.Millisecond),
|
||||||
stats: sstats,
|
SyncTicker: time.Tick(500 * time.Millisecond),
|
||||||
lstats: lstats,
|
snapCount: cfg.SnapCount,
|
||||||
Ticker: time.Tick(100 * time.Millisecond),
|
|
||||||
SyncTicker: time.Tick(500 * time.Millisecond),
|
|
||||||
snapCount: cfg.SnapCount,
|
|
||||||
}
|
}
|
||||||
srv.sendhub = newSendHub(cfg.Transport, cfg.Cluster, srv, sstats, lstats)
|
srv.sendhub = newSendHub(cfg.Transport, cfg.Cluster, srv, sstats, lstats)
|
||||||
for _, m := range getOtherMembers(cfg.Cluster, cfg.Name) {
|
for _, m := range getOtherMembers(cfg.Cluster, cfg.Name) {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package etcdserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coreos/etcd/raft/raftpb"
|
||||||
|
"github.com/coreos/etcd/snap"
|
||||||
|
"github.com/coreos/etcd/wal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage interface {
|
||||||
|
// Save function saves ents and state to the underlying stable storage.
|
||||||
|
// Save MUST block until st and ents are on stable storage.
|
||||||
|
Save(st raftpb.HardState, ents []raftpb.Entry) error
|
||||||
|
// SaveSnap function saves snapshot to the underlying stable storage.
|
||||||
|
SaveSnap(snap raftpb.Snapshot) error
|
||||||
|
|
||||||
|
// TODO: WAL should be able to control cut itself. After implement self-controlled cut,
|
||||||
|
// remove it in this interface.
|
||||||
|
// Cut cuts out a new wal file for saving new state and entries.
|
||||||
|
Cut() error
|
||||||
|
// Close closes the Storage and performs finalization.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type storage struct {
|
||||||
|
*wal.WAL
|
||||||
|
*snap.Snapshotter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorage(w *wal.WAL, s *snap.Snapshotter) Storage {
|
||||||
|
return &storage{w, s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveSnap saves the snapshot to disk and release the locked
|
||||||
|
// wal files since they will not be used.
|
||||||
|
func (st *storage) SaveSnap(snap raftpb.Snapshot) error {
|
||||||
|
err := st.Snapshotter.SaveSnap(snap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = st.WAL.ReleaseLockTo(snap.Metadata.Index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package fileutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrLocked = errors.New("file already locked")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lock interface {
|
||||||
|
Name() string
|
||||||
|
TryLock() error
|
||||||
|
Lock() error
|
||||||
|
Unlock() error
|
||||||
|
Destroy() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type lock struct {
|
||||||
|
fd int
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lock) Name() string {
|
||||||
|
return l.file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryLock acquires exclusivity on the lock without blocking
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock acquires exclusivity on the lock without blocking
|
||||||
|
func (l *lock) Lock() error {
|
||||||
|
return syscall.Flock(l.fd, syscall.LOCK_EX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock unlocks the lock
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &lock{int(f.Fd()), f}
|
||||||
|
return l, nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package fileutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLockAndUnlock(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "lock")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer func() {
|
||||||
|
err := os.Remove(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// lock the file
|
||||||
|
l, err := NewLock(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Destroy()
|
||||||
|
err = l.Lock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try lock a locked file
|
||||||
|
dupl, err := NewLock(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
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 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try lock the unlocked file
|
||||||
|
err = dupl.TryLock()
|
||||||
|
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()
|
||||||
|
locked <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-locked:
|
||||||
|
t.Error("unexpected unblocking")
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlock
|
||||||
|
err = dupl.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the previously blocked routine should be unblocked
|
||||||
|
select {
|
||||||
|
case <-locked:
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
t.Error("unexpected blocking")
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,12 +27,29 @@ func PurgeFile(dirname string, suffix string, max uint, interval time.Duration,
|
||||||
sort.Strings(newfnames)
|
sort.Strings(newfnames)
|
||||||
for len(newfnames) > int(max) {
|
for len(newfnames) > int(max) {
|
||||||
f := path.Join(dirname, newfnames[0])
|
f := path.Join(dirname, newfnames[0])
|
||||||
err := os.Remove(f)
|
l, err := NewLock(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errC <- err
|
errC <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("filePurge: successfully remvoed file %s", f)
|
err = l.TryLock()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = os.Remove(f)
|
||||||
|
if err != nil {
|
||||||
|
errC <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = l.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("filePurge: unlock %s error %v", l.Name(), err)
|
||||||
|
}
|
||||||
|
err = l.Destroy()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("filePurge: destroy lock %s error %v", l.Name(), err)
|
||||||
|
}
|
||||||
|
log.Printf("filePurge: successfully removed file %s", f)
|
||||||
newfnames = newfnames[1:]
|
newfnames = newfnames[1:]
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -31,7 +31,7 @@ func TestPurgeFile(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(2 * time.Millisecond)
|
||||||
}
|
}
|
||||||
fnames, err := ReadDir(dir)
|
fnames, err := ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -48,3 +48,71 @@ func TestPurgeFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
close(stop)
|
close(stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPurgeFileHoldingLock(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "purgefile")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
_, err := os.Create(path.Join(dir, fmt.Sprintf("%d.test", i)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a purge barrier at 5
|
||||||
|
l, err := NewLock(path.Join(dir, fmt.Sprintf("%d.test", 5)))
|
||||||
|
err = l.Lock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := make(chan struct{})
|
||||||
|
errch := PurgeFile(dir, "test", 3, time.Millisecond, stop)
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
fnames, err := ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wnames := []string{"5.test", "6.test", "7.test", "8.test", "9.test"}
|
||||||
|
if !reflect.DeepEqual(fnames, wnames) {
|
||||||
|
t.Errorf("filenames = %v, want %v", fnames, wnames)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case err := <-errch:
|
||||||
|
t.Errorf("unexpected purge error %v", err)
|
||||||
|
case <-time.After(time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the purge barrier
|
||||||
|
err = l.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = l.Destroy()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
fnames, err = ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wnames = []string{"7.test", "8.test", "9.test"}
|
||||||
|
if !reflect.DeepEqual(fnames, wnames) {
|
||||||
|
t.Errorf("filenames = %v, want %v", fnames, wnames)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case err := <-errch:
|
||||||
|
t.Errorf("unexpected purge error %v", err)
|
||||||
|
case <-time.After(time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
close(stop)
|
||||||
|
}
|
||||||
|
|
65
wal/wal.go
65
wal/wal.go
|
@ -67,6 +67,8 @@ type WAL struct {
|
||||||
seq uint64 // sequence of the wal file currently used for writes
|
seq uint64 // sequence of the wal file currently used for writes
|
||||||
enti uint64 // index of the last entry saved to the wal
|
enti uint64 // index of the last entry saved to the wal
|
||||||
encoder *encoder // encoder to encode records
|
encoder *encoder // encoder to encode records
|
||||||
|
|
||||||
|
locks []fileutil.Lock // the file locks the WAL is holding (the name is increasing)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a WAL ready for appending records. The given metadata is
|
// Create creates a WAL ready for appending records. The given metadata is
|
||||||
|
@ -85,6 +87,15 @@ func Create(dirpath string, metadata []byte) (*WAL, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
l, err := fileutil.NewLock(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = l.Lock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
w := &WAL{
|
w := &WAL{
|
||||||
dir: dirpath,
|
dir: dirpath,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
@ -92,6 +103,7 @@ func Create(dirpath string, metadata []byte) (*WAL, error) {
|
||||||
f: f,
|
f: f,
|
||||||
encoder: newEncoder(f, 0),
|
encoder: newEncoder(f, 0),
|
||||||
}
|
}
|
||||||
|
w.locks = append(w.locks, l)
|
||||||
if err := w.saveCrc(0); err != nil {
|
if err := w.saveCrc(0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -129,12 +141,22 @@ func OpenAtIndex(dirpath string, index uint64) (*WAL, error) {
|
||||||
|
|
||||||
// open the wal files for reading
|
// open the wal files for reading
|
||||||
rcs := make([]io.ReadCloser, 0)
|
rcs := make([]io.ReadCloser, 0)
|
||||||
|
ls := make([]fileutil.Lock, 0)
|
||||||
for _, name := range names[nameIndex:] {
|
for _, name := range names[nameIndex:] {
|
||||||
f, err := os.Open(path.Join(dirpath, name))
|
f, err := os.Open(path.Join(dirpath, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
l, err := fileutil.NewLock(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = l.TryLock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
rcs = append(rcs, f)
|
rcs = append(rcs, f)
|
||||||
|
ls = append(ls, l)
|
||||||
}
|
}
|
||||||
rc := MultiReadCloser(rcs...)
|
rc := MultiReadCloser(rcs...)
|
||||||
|
|
||||||
|
@ -157,8 +179,9 @@ func OpenAtIndex(dirpath string, index uint64) (*WAL, error) {
|
||||||
ri: index,
|
ri: index,
|
||||||
decoder: newDecoder(rc),
|
decoder: newDecoder(rc),
|
||||||
|
|
||||||
f: f,
|
f: f,
|
||||||
seq: seq,
|
seq: seq,
|
||||||
|
locks: ls,
|
||||||
}
|
}
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
@ -224,6 +247,15 @@ func (w *WAL) Cut() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
l, err := fileutil.NewLock(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = l.Lock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.locks = append(w.locks, l)
|
||||||
if err = w.sync(); err != nil {
|
if err = w.sync(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -255,6 +287,30 @@ func (w *WAL) sync() error {
|
||||||
return w.f.Sync()
|
return w.f.Sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReleaseLockTo releases the locks w is holding, which
|
||||||
|
// have index smaller or equal to the given index.
|
||||||
|
func (w *WAL) ReleaseLockTo(index uint64) error {
|
||||||
|
for _, l := range w.locks {
|
||||||
|
_, i, err := parseWalName(path.Base(l.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if i > index {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = l.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = l.Destroy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.locks = w.locks[1:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WAL) Close() error {
|
func (w *WAL) Close() error {
|
||||||
if w.f != nil {
|
if w.f != nil {
|
||||||
if err := w.sync(); err != nil {
|
if err := w.sync(); err != nil {
|
||||||
|
@ -264,6 +320,11 @@ func (w *WAL) Close() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, l := range w.locks {
|
||||||
|
// TODO: log the error
|
||||||
|
l.Unlock()
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,7 @@ func TestRecover(t *testing.T) {
|
||||||
if !reflect.DeepEqual(state, s) {
|
if !reflect.DeepEqual(state, s) {
|
||||||
t.Errorf("state = %+v, want %+v", state, s)
|
t.Errorf("state = %+v, want %+v", state, s)
|
||||||
}
|
}
|
||||||
|
w.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchIndex(t *testing.T) {
|
func TestSearchIndex(t *testing.T) {
|
||||||
|
@ -365,6 +366,7 @@ func TestRecoverAfterCut(t *testing.T) {
|
||||||
t.Errorf("#%d: ents[%d].Index = %+v, want %+v", i, j, e.Index, j+i)
|
t.Errorf("#%d: ents[%d].Index = %+v, want %+v", i, j, e.Index, j+i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
w.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue