// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := os.MkdirAll(tmpdirpath, fileutil.PrivateDirMode); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err := f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err := fileutil.Preallocate(f.File, segmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) 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.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } if err := os.RemoveAll(dirpath); err != nil { return nil, err } if err := os.Rename(tmpdirpath, dirpath); err != nil { return nil, err } w.fp = newFilePipeline(w.dir, segmentSizeBytes) return w, nil }
func (fp *filePipeline) alloc() (f *fileutil.LockedFile, err error) { fpath := path.Join(fp.dir, fmt.Sprintf("%d.tmp", fp.count)) if f, err = fileutil.LockFile(fpath, os.O_CREATE|os.O_WRONLY, 0600); err != nil { return nil, err } if err = fileutil.Preallocate(f.File, fp.size, true); err != nil { plog.Errorf("failed to allocate space when creating new wal file (%v)", err) f.Close() return nil, err } fp.count++ return f, nil }
func (fp *filePipeline) alloc() (f *fileutil.LockedFile, err error) { // count % 2 so this file isn't the same as the one last published fpath := path.Join(fp.dir, fmt.Sprintf("%d.tmp", fp.count%2)) if f, err = fileutil.LockFile(fpath, os.O_CREATE|os.O_WRONLY, fileutil.PrivateFileMode); err != nil { return nil, err } if err = fileutil.Preallocate(f.File, fp.size, true); err != nil { plog.Errorf("failed to allocate space when creating new wal file (%v)", err) f.Close() return nil, err } fp.count++ return f, nil }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } if err := os.MkdirAll(dirpath, privateDirMode); err != nil { return nil, err } p := path.Join(dirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return nil, err } if _, err := f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err := fileutil.Preallocate(f.File, segmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), fp: newFilePipeline(dirpath, segmentSizeBytes), } w.locks = append(w.locks, f) 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.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } return w, nil }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err = f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err = fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) 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.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } if w, err = w.renameWal(tmpdirpath); err != nil { return nil, err } // 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 }
// Create creates a WAL ready for appending records. The given metadata is // recorded at the head of each WAL file, and can be retrieved with ReadAll. func Create(dirpath string, metadata []byte) (*WAL, error) { if Exist(dirpath) { return nil, os.ErrExist } // keep temporary wal directory so WAL initialization appears atomic tmpdirpath := path.Clean(dirpath) + ".tmp" if fileutil.Exist(tmpdirpath) { if err := os.RemoveAll(tmpdirpath); err != nil { return nil, err } } if err := fileutil.CreateDirAll(tmpdirpath); err != nil { return nil, err } p := path.Join(tmpdirpath, walName(0, 0)) f, err := fileutil.LockFile(p, os.O_WRONLY|os.O_CREATE, fileutil.PrivateFileMode) if err != nil { return nil, err } if _, err := f.Seek(0, os.SEEK_END); err != nil { return nil, err } if err := fileutil.Preallocate(f.File, SegmentSizeBytes, true); err != nil { return nil, err } w := &WAL{ dir: dirpath, metadata: metadata, encoder: newEncoder(f, 0), } w.locks = append(w.locks, f) 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.SaveSnapshot(walpb.Snapshot{}); err != nil { return nil, err } // rename of directory with locked files doesn't work on windows; close // the WAL to release the locks so the directory can be renamed w.Close() if err := os.Rename(tmpdirpath, dirpath); err != nil { return nil, err } // reopen and relock newWAL, oerr := Open(dirpath, walpb.Snapshot{}) if oerr != nil { return nil, oerr } if _, _, _, err := newWAL.ReadAll(); err != nil { newWAL.Close() return nil, err } return newWAL, nil }
// cut closes current file written and creates a new one ready to append. // cut first creates a temp wal file and writes necessary headers into it. // Then cut atomtically rename temp wal file to a wal file. func (w *WAL) cut() error { // close old wal file 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)) ftpath := fpath + ".tmp" // create a temp wal file with name sequence + 1, or tuncate the existing one ft, err := os.OpenFile(ftpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err } // update writer and save the previous crc w.f = ft prevCrc := w.encoder.crc.Sum32() w.encoder = newEncoder(w.f, prevCrc) if err := w.saveCrc(prevCrc); err != nil { return err } if err := w.encoder.encode(&walpb.Record{Type: metadataType, Data: w.metadata}); err != nil { return err } if err := w.saveState(&w.state); err != nil { return err } // close temp 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 } // open the wal file and update writer again f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND, 0600) if err != nil { return err } err = fileutil.Preallocate(f, segmentSizeBytes) if 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 } err = l.Lock() if 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 }
func openAtIndex(dirpath string, snap walpb.Snapshot, write bool) (*WAL, error) { names, err := fileutil.ReadDir(dirpath) if err != nil { return nil, err } names = checkWalNames(names) if len(names) == 0 { return nil, ErrFileNotFound } nameIndex, ok := searchIndex(names, snap.Index) if !ok || !isValidSeq(names[nameIndex:]) { return nil, ErrFileNotFound } // open the wal files for reading rcs := make([]io.ReadCloser, 0) ls := make([]fileutil.Lock, 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 { return nil, err } } rcs = append(rcs, f) ls = append(ls, l) } rc := MultiReadCloser(rcs...) // create a WAL ready for reading w := &WAL{ dir: dirpath, start: snap, decoder: newDecoder(rc), locks: ls, } if write { // open the lastest wal file for appending seq, _, err := parseWalName(names[len(names)-1]) if 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 { 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 }
func openAtIndex(dirpath string, snap walpb.Snapshot, write bool) (*WAL, error) { names, err := fileutil.ReadDir(dirpath) if err != nil { return nil, err } names = checkWalNames(names) if len(names) == 0 { return nil, ErrFileNotFound } nameIndex, ok := searchIndex(names, snap.Index) if !ok || !isValidSeq(names[nameIndex:]) { return nil, ErrFileNotFound } // open the wal files rcs := make([]io.ReadCloser, 0) rs := make([]io.Reader, 0) ls := make([]*fileutil.LockedFile, 0) for _, name := range names[nameIndex:] { p := path.Join(dirpath, name) if write { l, err := fileutil.TryLockFile(p, os.O_RDWR, 0600) if err != nil { closeAll(rcs...) return nil, err } ls = append(ls, l) rcs = append(rcs, l) } else { rf, err := os.OpenFile(p, os.O_RDONLY, 0600) if err != nil { closeAll(rcs...) return nil, err } ls = append(ls, nil) rcs = append(rcs, rf) } rs = append(rs, rcs[len(rcs)-1]) } closer := func() error { return closeAll(rcs...) } // create a WAL ready for reading w := &WAL{ dir: dirpath, start: snap, decoder: newDecoder(rs...), readClose: closer, locks: ls, } if write { // write reuses the file descriptors from read; don't close so // WAL can append without dropping the file lock w.readClose = nil if _, _, err := parseWalName(path.Base(w.tail().Name())); err != nil { closer() return nil, err } // don't resize file for preallocation in case tail is corrupted if err := fileutil.Preallocate(w.tail().File, segmentSizeBytes, false); err != nil { closer() plog.Errorf("failed to allocate space when creating new wal file (%v)", err) return nil, err } w.fp = newFilePipeline(w.dir, segmentSizeBytes) } return w, nil }