Skip to content

Commit 96e2dc7

Browse files
committed
pagecache: add test suite
1 parent 16d328e commit 96e2dc7

File tree

11 files changed

+1035
-114
lines changed

11 files changed

+1035
-114
lines changed

catfs/mio/pagecache/cache.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,21 @@ import (
44
"github.com/sahib/brig/catfs/mio/pagecache/page"
55
)
66

7+
// Cache is the backing layer that stores pages in memory
8+
// or whatever medium it choses to use.
79
type Cache interface {
8-
Evict(inode int32) error
9-
Lookup(inode, page int32) (*page.Page, error)
10-
Merge(inode, pageID, off int32, buf []byte) error
10+
// Lookup returns a cached page, identified by `inode` and `page`.
11+
// If there is no such page page.ErrCacheMiss is returned.
12+
Lookup(inode int64, page uint32) (*page.Page, error)
13+
14+
// Merge the existing cache contents with the new write
15+
// to `pageID`, starting at `pageOff` and with the contents of `buf`.
16+
Merge(inode int64, pageID, pageOff uint32, buf []byte) error
17+
18+
// Evict clears cached pages for `inode`. `size` can be used
19+
// to clear only up to a certain size.
20+
Evict(inode, size int64) error
21+
22+
// Close the cache and free up all resources.
1123
Close() error
1224
}

catfs/mio/pagecache/dircache/l1.go renamed to catfs/mio/pagecache/mdcache/l1.go

+31-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
package dircache
1+
package mdcache
22

33
import (
44
"container/list"
5-
"errors"
65
"fmt"
76

87
"github.com/sahib/brig/catfs/mio/pagecache/page"
@@ -37,31 +36,41 @@ type l1item struct {
3736
type l1cache struct {
3837
m map[pageKey]l1item
3938
k *list.List
40-
l2 *l2cache
39+
l2 cacheLayer
4140
maxMemory int64
4241
}
4342

44-
func newL1Cache(l2 *l2cache, maxMemory int64) (*l1cache, error) {
43+
func newL1Cache(l2 cacheLayer, maxMemory int64) (*l1cache, error) {
4544
return &l1cache{
4645
maxMemory: maxMemory,
4746
l2: l2,
4847
k: list.New(),
48+
m: make(map[pageKey]l1item),
4949
}, nil
5050
}
5151

5252
func (c *l1cache) Set(pk pageKey, p *page.Page) error {
53-
c.m[pk] = l1item{
54-
Page: p,
55-
Link: c.k.PushBack(pk),
53+
existingItem, ok := c.m[pk]
54+
if !ok {
55+
// new content:
56+
c.m[pk] = l1item{
57+
Page: p,
58+
Link: c.k.PushBack(pk),
59+
}
60+
} else {
61+
// do not push another page key,
62+
// c.k needs to have unique keys only.
63+
c.m[pk] = l1item{
64+
Page: p,
65+
Link: existingItem.Link,
66+
}
67+
68+
// prioritize this one more.
69+
c.k.MoveToBack(existingItem.Link)
5670
}
5771

5872
maxPages := c.maxMemory / (page.Size + page.Meta)
5973
if int64(len(c.m)) > maxPages {
60-
if c.l2 == nil {
61-
// just in case l2 cache was not given:
62-
return errors.New("cache is full")
63-
}
64-
6574
oldPkIface := c.k.Remove(c.k.Front())
6675
oldPk, ok := oldPkIface.(pageKey)
6776
if !ok {
@@ -73,7 +82,13 @@ func (c *l1cache) Set(pk pageKey, p *page.Page) error {
7382
if !ok {
7483
// c.m and c.k got out of sync.
7584
// this is very likely a bug.
76-
return fmt.Errorf("l1: key in key list, but not in map")
85+
return fmt.Errorf("l1: key in key list, but not in map: %v", oldPk)
86+
}
87+
88+
if c.l2 == nil {
89+
// nil-interface for l2: loose pages in that case.
90+
// that may be valid if no disk can be used.
91+
return nil
7792
}
7893

7994
// move old page to more persistent cache layer:
@@ -107,5 +122,8 @@ func (c *l1cache) Del(pks []pageKey) error {
107122
}
108123

109124
func (c *l1cache) Close() error {
125+
// help GC if caller somehow still retains a reference:
126+
c.m = nil
127+
c.k = nil
110128
return nil
111129
}
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package mdcache
2+
3+
import (
4+
"testing"
5+
6+
"github.com/sahib/brig/catfs/mio/pagecache/page"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func withL1Cache(t *testing.T, fn func(l1, backing *l1cache)) {
11+
// some fake in-mem cache that stores everything that got removed
12+
// out of l1 due to size restrictions.
13+
backing, err := newL1Cache(nil, int64(^uint64(0)>>1))
14+
require.NoError(t, err)
15+
16+
l1, err := newL1Cache(backing, 4*(page.Size+page.Meta))
17+
require.NoError(t, err)
18+
19+
fn(l1, backing)
20+
21+
require.NoError(t, l1.Close())
22+
}
23+
24+
func TestL1GetSetDel(t *testing.T) {
25+
// NOTE: Only covers the very basic usage.
26+
withL1Cache(t, func(l1, _ *l1cache) {
27+
pk := pageKey{1, 0}
28+
_, err := l1.Get(pk)
29+
require.Error(t, page.ErrCacheMiss)
30+
31+
pset := dummyPage(0, 1024)
32+
require.NoError(t, l1.Set(pk, pset))
33+
34+
pgot, err := l1.Get(pk)
35+
require.NoError(t, err)
36+
37+
require.Equal(t, pset.Data, pgot.Data)
38+
require.Equal(t, pset.Extents, pgot.Extents)
39+
40+
require.NoError(t, l1.Del([]pageKey{pk}))
41+
_, err = l1.Get(pk)
42+
require.Error(t, page.ErrCacheMiss)
43+
})
44+
}
45+
46+
func TestL1SwapPriority(t *testing.T) {
47+
withL1Cache(t, func(l1, backing *l1cache) {
48+
// Insert 8 pages, only 4 can stay in l1.
49+
for idx := 0; idx < 8; idx++ {
50+
pk := pageKey{1, uint32(idx)}
51+
require.NoError(t, l1.Set(pk, dummyPage(0, uint32((idx+1)*100))))
52+
}
53+
54+
for idx := 0; idx < 4; idx++ {
55+
pk := pageKey{1, uint32(idx)}
56+
_, err := l1.Get(pk)
57+
require.Error(t, err, page.ErrCacheMiss)
58+
59+
// should be in backing store, check:
60+
p, err := backing.Get(pk)
61+
require.NoError(t, err)
62+
63+
expected := dummyPage(0, uint32((idx+1)*100))
64+
require.Equal(t, expected, p)
65+
}
66+
67+
for idx := 4; idx < 8; idx++ {
68+
pk := pageKey{1, uint32(idx)}
69+
p, err := l1.Get(pk)
70+
require.NoError(t, err)
71+
72+
expected := dummyPage(0, uint32((idx+1)*100))
73+
require.Equal(t, expected, p)
74+
}
75+
})
76+
}

catfs/mio/pagecache/dircache/l2.go renamed to catfs/mio/pagecache/mdcache/l2.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dircache
1+
package mdcache
22

33
import (
44
"fmt"
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88

99
"github.com/sahib/brig/catfs/mio/pagecache/page"
10+
log "github.com/sirupsen/logrus"
1011
)
1112

1213
type l2cache struct {
@@ -35,16 +36,12 @@ func newL2Cache(dir string) (*l2cache, error) {
3536
}
3637

3738
func (c *l2cache) Set(pk pageKey, p *page.Page) error {
38-
return c.SetData(pk.String(), p.AsBytes())
39-
}
40-
41-
func (c *l2cache) SetData(key string, pdata []byte) error {
4239
if c == nil {
4340
return nil
4441
}
4542

46-
path := filepath.Join(c.dir, key)
47-
return ioutil.WriteFile(path, pdata, 0600)
43+
path := filepath.Join(c.dir, pk.String())
44+
return ioutil.WriteFile(path, p.AsBytes(), 0600)
4845
}
4946

5047
func (c *l2cache) Get(pk pageKey) (*page.Page, error) {
@@ -70,6 +67,7 @@ func (c *l2cache) Del(pks []pageKey) error {
7067
path := filepath.Join(c.dir, pk.String())
7168
if err := os.Remove(path); err != nil {
7269
// only log, we want to get rid of more old data.
70+
log.Warnf("page l2: failed to delete %s", path)
7371
}
7472
}
7573

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package mdcache
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"testing"
7+
8+
"github.com/sahib/brig/catfs/mio/pagecache/page"
9+
"github.com/sahib/brig/util/testutil"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func dummyPage(off, length uint32) *page.Page {
14+
buf := testutil.CreateDummyBuf(int64(length))
15+
return page.New(off, buf)
16+
}
17+
18+
func withL2Cache(t *testing.T, fn func(l2 *l2cache)) {
19+
tmpDir, err := ioutil.TempDir("", "brig-page-l2")
20+
require.NoError(t, err)
21+
defer os.RemoveAll(tmpDir)
22+
23+
l2, err := newL2Cache(tmpDir)
24+
require.NoError(t, err)
25+
26+
fn(l2)
27+
28+
// double check we do not waste any storage:
29+
require.NoError(t, l2.Close())
30+
_, err = os.Stat(tmpDir)
31+
require.True(t, os.IsNotExist(err))
32+
}
33+
34+
func TestL2GetSetDel(t *testing.T) {
35+
withL2Cache(t, func(l2 *l2cache) {
36+
pk := pageKey{1, 0}
37+
_, err := l2.Get(pk)
38+
require.Error(t, page.ErrCacheMiss)
39+
40+
pset := dummyPage(0, 1024)
41+
require.NoError(t, l2.Set(pk, pset))
42+
43+
pgot, err := l2.Get(pk)
44+
require.NoError(t, err)
45+
46+
require.Equal(t, pset.Data, pgot.Data)
47+
require.Equal(t, pset.Extents, pgot.Extents)
48+
49+
require.NoError(t, l2.Del([]pageKey{pk}))
50+
_, err = l2.Get(pk)
51+
require.Error(t, page.ErrCacheMiss)
52+
})
53+
}
54+
55+
func TestL2Nil(t *testing.T) {
56+
// l2 is optional, so a nil l2 cache should "work":
57+
l2, err := newL2Cache("")
58+
require.NoError(t, err)
59+
60+
_, err = l2.Get(pageKey{0, 1})
61+
require.Error(t, page.ErrCacheMiss)
62+
63+
require.NoError(t, l2.Set(pageKey{0, 1}, dummyPage(0, 1024)))
64+
require.NoError(t, l2.Del([]pageKey{{0, 1}}))
65+
}

0 commit comments

Comments
 (0)