diff --git a/pkg/fileutil/dir_unix.go b/pkg/fileutil/dir_unix.go new file mode 100644 index 00000000000..58a77dfc1a9 --- /dev/null +++ b/pkg/fileutil/dir_unix.go @@ -0,0 +1,22 @@ +// 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. + +// +build !windows + +package fileutil + +import "os" + +// OpenDir opens a directory for syncing. +func OpenDir(path string) (*os.File, error) { return os.Open(path) } diff --git a/pkg/fileutil/dir_windows.go b/pkg/fileutil/dir_windows.go new file mode 100644 index 00000000000..c123395c004 --- /dev/null +++ b/pkg/fileutil/dir_windows.go @@ -0,0 +1,46 @@ +// 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. + +// +build windows + +package fileutil + +import ( + "os" + "syscall" +) + +// OpenDir opens a directory in windows with write access for syncing. +func OpenDir(path string) (*os.File, error) { + fd, err := openDir(path) + if err != nil { + return nil, err + } + return os.NewFile(uintptr(fd), path), nil +} + +func openDir(path string) (fd syscall.Handle, err error) { + if len(path) == 0 { + return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND + } + pathp, err := syscall.UTF16PtrFromString(path) + if err != nil { + return syscall.InvalidHandle, err + } + access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE) + sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE) + createmode := uint32(syscall.OPEN_EXISTING) + fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) + return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0) +} diff --git a/wal/wal.go b/wal/wal.go index 13193c064a4..377f35fef48 100644 --- a/wal/wal.go +++ b/wal/wal.go @@ -69,7 +69,11 @@ var ( // A just opened WAL is in read mode, and ready for reading records. // The WAL will be ready for appending after reading out all the previous records. type WAL struct { - dir string // the living directory of the underlay files + dir string // the living directory of the underlay files + + // dirFile is a fd for the wal directory for syncing on Rename + dirFile *os.File + metadata []byte // metadata recorded at the head of each WAL state raftpb.HardState // hardstate recorded at the head of WAL @@ -108,10 +112,10 @@ func Create(dirpath string, metadata []byte) (*WAL, error) { if err != nil { return nil, err } - if _, err := f.Seek(0, os.SEEK_END); err != nil { + if _, err = f.Seek(0, os.SEEK_END); err != nil { return nil, err } - if err := fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { + if err = fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } @@ -121,17 +125,33 @@ func Create(dirpath string, metadata []byte) (*WAL, error) { encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) - if err := w.saveCrc(0); err != nil { + if err = w.saveCrc(0); err != nil { + return nil, err + } + if err = w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { return nil, err } - if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: metadata}); err != nil { + if err = w.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } - if err := w.SaveSnapshot(walpb.Snapshot{}); err != nil { + + if w, err = w.renameWal(tmpdirpath); err != nil { return nil, err } - return w.renameWal(tmpdirpath) + // directory was renamed; sync parent dir to persist rename + pdir, perr := fileutil.OpenDir(path.Dir(w.dir)) + if perr != nil { + return nil, perr + } + if perr = fileutil.Fsync(pdir); perr != nil { + return nil, perr + } + if perr = pdir.Close(); err != nil { + return nil, perr + } + + return w, nil } // Open opens the WAL at the given snap. @@ -141,7 +161,14 @@ func Create(dirpath string, metadata []byte) (*WAL, error) { // the given snap. The WAL cannot be appended to before reading out all of its // previous records. func Open(dirpath string, snap walpb.Snapshot) (*WAL, error) { - return openAtIndex(dirpath, snap, true) + w, err := openAtIndex(dirpath, snap, true) + if err != nil { + return nil, err + } + if w.dirFile, err = fileutil.OpenDir(w.dir); err != nil { + return nil, err + } + return w, nil } // OpenForRead only opens the wal files for read. @@ -373,6 +400,10 @@ func (w *WAL) cut() error { if err = os.Rename(newTail.Name(), fpath); err != nil { return err } + if err = fileutil.Fsync(w.dirFile); err != nil { + return err + } + newTail.Close() if newTail, err = fileutil.LockFile(fpath, os.O_WRONLY, fileutil.PrivateFileMode); err != nil { @@ -475,6 +506,11 @@ func (w *WAL) Close() error { plog.Errorf("failed to unlock during closing wal: %s", err) } } + + if err := w.dirFile.Close(); err != nil { + return err + } + return nil } diff --git a/wal/wal_unix.go b/wal/wal_unix.go index 101ea6acc3c..82fd6a17a74 100644 --- a/wal/wal_unix.go +++ b/wal/wal_unix.go @@ -16,7 +16,11 @@ package wal -import "os" +import ( + "os" + + "github.com/coreos/etcd/pkg/fileutil" +) func (w *WAL) renameWal(tmpdirpath string) (*WAL, error) { // On non-Windows platforms, hold the lock while renaming. Releasing @@ -34,5 +38,7 @@ func (w *WAL) renameWal(tmpdirpath string) (*WAL, error) { } w.fp = newFilePipeline(w.dir, SegmentSizeBytes) - return w, nil + df, err := fileutil.OpenDir(w.dir) + w.dirFile = df + return w, err }