Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions diskcache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ diskcache 是一种类似 wal 的磁盘缓存,它有如下特性:
限制:

- 不支持随机读取,只支持按照 FIFO 的顺序来消费数据
- `Close()` 是终止操作;关闭后的读写和 rotate 操作会返回 `ErrClosed`
- `.lock` 是持久标记文件;`Close()` 只释放文件锁,不删除该文件

## 实现算法

Expand Down
8 changes: 8 additions & 0 deletions diskcache/diskcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ var (
// Diskcache full, no data can be write now.
ErrCacheFull = errors.New("cache full")

// ErrClosed indicates an operation was attempted on a closed cache.
ErrClosed = errors.New("diskcache closed")

ErrInvalidStreamSize = errors.New("invalid stream size")

// Invalid cache filename.
Expand All @@ -72,6 +75,11 @@ type DiskCache struct {

dataFiles []string

// lifecycleMu excludes Close from in-flight I/O and protects closed and closeErr.
lifecycleMu sync.RWMutex
closed bool
closeErr error

// current writing/reading file.
curWriteFile,
curReadfile string
Expand Down
10 changes: 6 additions & 4 deletions diskcache/flock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

func TestLockUnlock(t *T.T) {
t.Run("unlock-remove", func(t *T.T) {
t.Run("unlock-keeps-lock-file", func(t *T.T) {
p := t.TempDir()
fl := newFlock(p)

Expand All @@ -28,10 +28,10 @@ func TestLockUnlock(t *T.T) {
assert.NoError(t, err)
t.Logf("fi: %+#v", fi)

fl.unlock()
assert.NoError(t, fl.unlock())

_, err = os.Stat(filepath.Join(p, ".lock"))
assert.Error(t, err)
assert.NoError(t, err)
})

t.Run("lock", func(t *T.T) {
Expand All @@ -48,7 +48,9 @@ func TestLockUnlock(t *T.T) {

assert.True(t, ok)
assert.NoError(t, err)
defer fl.unlock()
defer func() {
assert.NoError(t, fl.unlock())
}()

time.Sleep(time.Second * 5)
}()
Expand Down
20 changes: 12 additions & 8 deletions diskcache/flock_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,22 @@ func (wl *walLock) tryLock() (bool, error) {
return true, nil
}

func (wl *walLock) unlock() {
func (wl *walLock) unlock() error {
if wl.f != nil {
if err := syscall.Flock(int(wl.f.Fd()), syscall.LOCK_UN); err != nil {
l.Errorf("Flock: %s", err.Error())
}
f := wl.f
wl.f = nil
var errs []error

if err := wl.f.Close(); err != nil {
l.Errorf("CLose: %s", err.Error())
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN); err != nil {
errs = append(errs, fmt.Errorf("unlock lock file: %w", err))
}

if err := os.Remove(wl.file); err != nil { // Optional on Unix
l.Errorf("Remove: %s", err.Error())
if err := f.Close(); err != nil {
errs = append(errs, fmt.Errorf("close lock file: %w", err))
}

return errors.Join(errs...)
}

return nil
}
9 changes: 6 additions & 3 deletions diskcache/flock_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ func (wl *walLock) tryLock() (bool, error) {
return true, nil
}

func (wl *walLock) unlock() {
func (wl *walLock) unlock() error {
if wl.f != nil {
wl.f.Close() // Closing the file handle automatically releases the lock in Windows
os.Remove(wl.file)
f := wl.f
wl.f = nil
return f.Close() // Closing the file handle automatically releases the lock in Windows
}

return nil
}
12 changes: 11 additions & 1 deletion diskcache/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
"time"
)

// Fn is the handler to eat cache from diskcache.
// Fn is the handler to consume data from diskcache.
// A callback must not synchronously call methods on the same DiskCache.
type Fn func([]byte) error

func (c *DiskCache) switchNextFile() error {
Expand Down Expand Up @@ -51,6 +52,8 @@ func (c *DiskCache) Get(fn Fn) error {
return c.doGet(nil, fn, nil)
}

// BufFunc supplies a buffer for BufCallbackGet.
// A callback must not synchronously call methods on the same DiskCache.
type BufFunc func() []byte

// BufCallbackGet fetch new data from disk cache, and read into buffer that returned by bfn.
Expand All @@ -70,6 +73,13 @@ func (c *DiskCache) doGet(buf []byte, fn Fn, bfn BufFunc) error {
err error
)

c.lifecycleMu.RLock()
defer c.lifecycleMu.RUnlock()

if c.closed {
return WrapGetError(ErrClosed, c.path, "")
}

c.rlock.Lock()
defer c.rlock.Unlock()

Expand Down
52 changes: 34 additions & 18 deletions diskcache/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package diskcache

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -186,42 +187,57 @@ func (c *DiskCache) doOpen() error {
return nil
}

// Close reclame fd resources.
// Close is safe to call concurrently with other operations and will
// block until all other operations finish.
// Close permanently closes the cache and reclaims its file descriptors.
// It waits for in-flight operations, is idempotent, and causes later I/O
// operations to return ErrClosed.
func (c *DiskCache) Close() error {
c.rwlock.Lock()
defer c.rwlock.Unlock()
c.lifecycleMu.Lock()
defer c.lifecycleMu.Unlock()

defer func() {
lastCloseTimeVec.WithLabelValues(c.path).Set(float64(time.Now().Unix()))
}()

if c.rfd != nil {
if err := c.rfd.Close(); err != nil {
return WrapCloseError(err, c.path, "read_fd")
}
c.rfd = nil
if c.closed {
return c.closeErr
}
c.closed = true

if !c.noLock {
if c.flock != nil {
c.flock.unlock()
c.rwlock.Lock()
defer c.rwlock.Unlock()

var errs []error

if c.rfd != nil {
fd := c.rfd
c.rfd = nil
if err := fd.Close(); err != nil {
errs = append(errs, WrapCloseError(err, c.path, "read_fd"))
}
}

if c.wfd != nil {
if err := c.wfd.Close(); err != nil {
return WrapCloseError(err, c.path, "write_fd")
}
fd := c.wfd
c.wfd = nil
if err := fd.Close(); err != nil {
errs = append(errs, WrapCloseError(err, c.path, "write_fd"))
}
}

if c.pos != nil {
if err := c.pos.close(); err != nil {
return WrapPosError(err, c.path, c.pos.Seek).WithDetails("failed_to_close_position_file")
errs = append(errs, WrapPosError(err, c.path, c.pos.Seek).WithDetails("failed_to_close_position_file"))
}
}

return nil
if !c.noLock && c.flock != nil {
flock := c.flock
c.flock = nil
if err := flock.unlock(); err != nil {
errs = append(errs, NewCacheError(OpUnlock, err, "failed_to_release_directory_lock").WithPath(c.path))
}
}

c.closeErr = errors.Join(errs...)
return c.closeErr
}
Loading
Loading