Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go.work.sum
2 changes: 1 addition & 1 deletion fastcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ var cacheNoStore = []byte("no-store")
// New creates and returns a new FastCache instance.
func New(s Store) *FastCache {
return &FastCache{
s: s,
s: newSingleflightStore(s),
}
}

Expand Down
62 changes: 62 additions & 0 deletions singleflight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package fastcache

import (
"fmt"
"time"

"golang.org/x/sync/singleflight"
)

// singleFlightStore wraps a Store with singleflight functionality
type singleFlightStore struct {
store Store
sf singleflight.Group
}

// newSingleflightStore creates a new SingleflightStore
func newSingleflightStore(store Store) *singleFlightStore {
return &singleFlightStore{
store: store,
}
}

// Get retrieves an item from the store using singleflight
func (s *singleFlightStore) Get(namespace, group, uri string) (Item, error) {
key := fmt.Sprintf("%s:%s:%s", namespace, group, uri)

v, err, _ := s.sf.Do(key, func() (interface{}, error) {
return s.store.Get(namespace, group, uri)
})

if err != nil {
return Item{}, err
}

// Handle the case where the item doesn't exist
if v == nil {
return Item{}, nil
}

// Check the type of v is Item
item, ok := v.(Item)
if !ok {
return Item{}, fmt.Errorf("unexpected type %T", v)
}

return item, nil
}

// Put adds an item to the underlying store
func (s *singleFlightStore) Put(namespace, group, uri string, b Item, ttl time.Duration) error {
return s.store.Put(namespace, group, uri, b, ttl)
}

// Del removes an item from the underlying store
func (s *singleFlightStore) Del(namespace, group, uri string) error {
return s.store.Del(namespace, group, uri)
}

// DelGroup removes a group of items from the underlying store
func (s *singleFlightStore) DelGroup(namespace string, group ...string) error {
return s.store.DelGroup(namespace, group...)
}
53 changes: 53 additions & 0 deletions singleflight_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package fastcache

import (
"sync"
"testing"
"time"
)

type testSlowStore struct {
getCount int
delay time.Duration
}

func (s *testSlowStore) Get(namespace, group, uri string) (Item, error) {
time.Sleep(s.delay)
s.getCount++
return Item{}, nil
}

func (s *testSlowStore) Put(namespace, group, uri string, b Item, ttl time.Duration) error {
return nil
}

func (s *testSlowStore) Del(namespace, group, uri string) error {
return nil
}

func (s *testSlowStore) DelGroup(namespace string, group ...string) error {
return nil
}

func TestSingleFlightStore(t *testing.T) {
slowStore := &testSlowStore{delay: 100 * time.Millisecond}
sfs := newSingleflightStore(slowStore)

sfs.Put("namespace", "group", "uri", Item{}, 0)

// Call Get 10 times concurrently
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
_, _ = sfs.Get("namespace", "group", "uri")
wg.Done()
}()
}

wg.Wait()

if slowStore.getCount != 1 {
t.Errorf("expected 1, got %d", slowStore.getCount)
}
}
Loading