Skip to content

Commit 387e703

Browse files
dgrisonnetdims
authored andcommitted
zfs: use atomic.Value for lock-free cache reads
The ZfsWatcher cache is read frequently by container housekeeping goroutines while being updated every 15 seconds by Refresh(). Using atomic.Value allows lock-free reads, eliminating contention between readers and the single writer. Add usageCache typed wrapper to eliminate boilerplate type assertions. Signed-off-by: Damien Grisonnet <[email protected]>
1 parent 317a38e commit 387e703

File tree

1 file changed

+27
-17
lines changed

1 file changed

+27
-17
lines changed

zfs/watcher.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,49 @@ package zfs
1616

1717
import (
1818
"fmt"
19-
"sync"
19+
"sync/atomic"
2020
"time"
2121

2222
zfs "github.com/mistifyio/go-zfs"
2323
"k8s.io/klog/v2"
2424
)
2525

26+
// usageCache is a typed wrapper around atomic.Value that eliminates the need
27+
// for type assertions at every call site. It stores filesystem name strings
28+
// mapped to usage values (uint64).
29+
type usageCache struct {
30+
v atomic.Value
31+
}
32+
33+
// Load retrieves the current cache map.
34+
func (c *usageCache) Load() map[string]uint64 {
35+
return c.v.Load().(map[string]uint64)
36+
}
37+
38+
// Store saves a new cache map.
39+
func (c *usageCache) Store(m map[string]uint64) {
40+
c.v.Store(m)
41+
}
42+
2643
// zfsWatcher maintains a cache of filesystem -> usage stats for a
2744
// zfs filesystem
2845
type ZfsWatcher struct {
2946
filesystem string
30-
lock *sync.RWMutex
31-
cache map[string]uint64
47+
cache usageCache
3248
period time.Duration
3349
stopChan chan struct{}
3450
}
3551

3652
// NewThinPoolWatcher returns a new ThinPoolWatcher for the given devicemapper
3753
// thin pool name and metadata device or an error.
3854
func NewZfsWatcher(filesystem string) (*ZfsWatcher, error) {
39-
40-
return &ZfsWatcher{
55+
w := &ZfsWatcher{
4156
filesystem: filesystem,
42-
lock: &sync.RWMutex{},
43-
cache: make(map[string]uint64),
4457
period: 15 * time.Second,
4558
stopChan: make(chan struct{}),
46-
}, nil
59+
}
60+
w.cache.Store(map[string]uint64{})
61+
return w, nil
4762
}
4863

4964
// Start starts the ZfsWatcher.
@@ -78,22 +93,16 @@ func (w *ZfsWatcher) Stop() {
7893

7994
// GetUsage gets the cached usage value of the given filesystem.
8095
func (w *ZfsWatcher) GetUsage(filesystem string) (uint64, error) {
81-
w.lock.RLock()
82-
defer w.lock.RUnlock()
83-
84-
v, ok := w.cache[filesystem]
96+
cache := w.cache.Load()
97+
v, ok := cache[filesystem]
8598
if !ok {
8699
return 0, fmt.Errorf("no cached value for usage of filesystem %v", filesystem)
87100
}
88-
89101
return v, nil
90102
}
91103

92104
// Refresh performs a zfs get
93105
func (w *ZfsWatcher) Refresh() error {
94-
w.lock.Lock()
95-
defer w.lock.Unlock()
96-
newCache := make(map[string]uint64)
97106
parent, err := zfs.GetDataset(w.filesystem)
98107
if err != nil {
99108
klog.Errorf("encountered error getting zfs filesystem: %s: %v", w.filesystem, err)
@@ -105,10 +114,11 @@ func (w *ZfsWatcher) Refresh() error {
105114
return err
106115
}
107116

117+
newCache := make(map[string]uint64)
108118
for _, ds := range children {
109119
newCache[ds.Name] = ds.Used
110120
}
111121

112-
w.cache = newCache
122+
w.cache.Store(newCache)
113123
return nil
114124
}

0 commit comments

Comments
 (0)