Commit d56fe396 authored by Kirill Smelkov's avatar Kirill Smelkov

X move to zodb/storage.Cache & xcontainer/list

parent 1e40e46d
...@@ -17,27 +17,30 @@ ...@@ -17,27 +17,30 @@
// See COPYING file for full licensing terms. // See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options. // See https://www.nexedi.com/licensing for rationale and options.
package client // Package list provides intrusive double-linked lists.
// base for intrusive list package list
// listHead is a list head entry for an element in an intrusive doubly-linked list. // Head is a list head entry for an element in an intrusive doubly-linked list.
// //
// XXX doc how to get to container of this list head via unsafe.OffsetOf // XXX doc how to get to container of this list head via unsafe.OffsetOf
// //
// always call Init() to initialize a head before using it. // always call Init() to initialize a head before using it.
type listHead struct { type Head struct {
// XXX needs to be created with .next = .prev = self // XXX needs to be created with .next = .prev = self
next, prev *listHead next, prev *Head
} }
func (h *Head) Next() *Head { return h.next }
func (h *Head) Prev() *Head { return h.prev }
// Init initializes a head making it point to itself via .next and .prev // Init initializes a head making it point to itself via .next and .prev
func (h *listHead) Init() { func (h *Head) Init() {
h.next = h h.next = h
h.prev = h h.prev = h
} }
// Delete deletes h from its list // Delete deletes h from its list
func (h *listHead) Delete() { func (h *Head) Delete() {
h.next.prev = h.prev h.next.prev = h.prev
h.prev.next = h.next h.prev.next = h.next
h.Init() h.Init()
...@@ -45,7 +48,7 @@ func (h *listHead) Delete() { ...@@ -45,7 +48,7 @@ func (h *listHead) Delete() {
// MoveBefore moves a to be before b // MoveBefore moves a to be before b
// XXX ok to move if a was not previously on the list? // XXX ok to move if a was not previously on the list?
func (a *listHead) MoveBefore(b *listHead) { func (a *Head) MoveBefore(b *Head) {
a.Delete() a.Delete()
a.next = b a.next = b
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// See COPYING file for full licensing terms. // See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options. // See https://www.nexedi.com/licensing for rationale and options.
package client package storage
// cache management // cache management
// XXX gotrace ... -> gotrace gen ... // XXX gotrace ... -> gotrace gen ...
...@@ -29,19 +29,16 @@ import ( ...@@ -29,19 +29,16 @@ import (
"sync" "sync"
"unsafe" "unsafe"
// "github.com/kylelemons/godebug/pretty"
"lab.nexedi.com/kirr/neo/go/zodb" "lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/neo/go/xcommon/xcontainer/list"
) )
// storLoader represents loading part of a storage // TODO maintain nhit / nmiss + way to read cache stats
// XXX -> zodb?
type storLoader interface {
Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error)
}
// Cache adds RAM caching layer over a storage // Cache adds RAM caching layer over a storage.
type Cache struct { type Cache struct {
loader storLoader loader StorLoader
mu sync.RWMutex mu sync.RWMutex
...@@ -55,7 +52,7 @@ type Cache struct { ...@@ -55,7 +52,7 @@ type Cache struct {
gcCh chan struct{} // signals gc to run gcCh chan struct{} // signals gc to run
gcMu sync.Mutex gcMu sync.Mutex
lru listHead // revCacheEntries in LRU order lru lruHead // revCacheEntries in LRU order
size int // cached data size in bytes size int // cached data size in bytes
sizeMax int // cache is allowed to occupy not more than this sizeMax int // cache is allowed to occupy not more than this
...@@ -80,7 +77,7 @@ type oidCacheEntry struct { ...@@ -80,7 +77,7 @@ type oidCacheEntry struct {
// revCacheEntry is information about 1 cached oid revision // revCacheEntry is information about 1 cached oid revision
type revCacheEntry struct { type revCacheEntry struct {
parent *oidCacheEntry // oidCacheEntry holding us parent *oidCacheEntry // oidCacheEntry holding us
inLRU listHead // in Cache.lru; protected by Cache.gcMu inLRU lruHead // in Cache.lru; protected by Cache.gcMu
// we know that loadBefore(oid, .before) will give this .serial:oid. // we know that loadBefore(oid, .before) will give this .serial:oid.
// //
...@@ -107,16 +104,20 @@ type revCacheEntry struct { ...@@ -107,16 +104,20 @@ type revCacheEntry struct {
accounted bool // whether rce size accounted in cache size; protected by .parent's lock accounted bool // whether rce size accounted in cache size; protected by .parent's lock
} }
// StorLoader represents loading part of a storage.
// XXX -> zodb?
type StorLoader interface {
Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error)
}
// lock order: Cache.mu > oidCacheEntry // lock order: Cache.mu > oidCacheEntry
// Cache.gcMu > oidCacheEntry // Cache.gcMu > oidCacheEntry
// XXX maintain nhit / nmiss?
// NewCache creates new cache backed up by loader. // NewCache creates new cache backed up by loader.
// //
// The cache will use not more than ~ sizeMax bytes of RAM for cached data. // The cache will use not more than ~ sizeMax bytes of RAM for cached data.
func NewCache(loader storLoader, sizeMax int) *Cache { func NewCache(loader StorLoader, sizeMax int) *Cache {
c := &Cache{ c := &Cache{
loader: loader, loader: loader,
entryMap: make(map[zodb.Oid]*oidCacheEntry), entryMap: make(map[zodb.Oid]*oidCacheEntry),
...@@ -146,7 +147,7 @@ func (c *Cache) SetSizeMax(sizeMax int) { ...@@ -146,7 +147,7 @@ func (c *Cache) SetSizeMax(sizeMax int) {
// Load loads data from database via cache. // Load loads data from database via cache.
// //
// If data is already in cache cached content is returned. // If data is already in cache - cached content is returned.
func (c *Cache) Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error) { func (c *Cache) Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error) {
rce, rceNew := c.lookupRCE(xid) rce, rceNew := c.lookupRCE(xid)
...@@ -154,7 +155,7 @@ func (c *Cache) Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error) { ...@@ -154,7 +155,7 @@ func (c *Cache) Load(xid zodb.Xid) (data []byte, serial zodb.Tid, err error) {
if !rceNew { if !rceNew {
<-rce.ready <-rce.ready
c.gcMu.Lock() c.gcMu.Lock()
rce.inLRU.MoveBefore(&c.lru) rce.inLRU.MoveBefore(&c.lru.Head)
c.gcMu.Unlock() c.gcMu.Unlock()
// rce is not in cache - this goroutine becomes responsible for loading it // rce is not in cache - this goroutine becomes responsible for loading it
...@@ -366,23 +367,19 @@ func (c *Cache) loadRCE(rce *revCacheEntry, oid zodb.Oid) { ...@@ -366,23 +367,19 @@ func (c *Cache) loadRCE(rce *revCacheEntry, oid zodb.Oid) {
// update lru & cache size // update lru & cache size
gcrun := false gcrun := false
c.gcMu.Lock() c.gcMu.Lock()
//xv1 := map[string]interface{}{"lru": &c.lru, "rce": &rce.inLRU}
//fmt.Printf("aaa:\n%s\n", pretty.Sprint(xv1))
if rcePrevDropped != nil { if rcePrevDropped != nil {
rcePrevDropped.inLRU.Delete() rcePrevDropped.inLRU.Delete()
} }
if !rceDropped { if !rceDropped {
rce.inLRU.MoveBefore(&c.lru) rce.inLRU.MoveBefore(&c.lru.Head)
} }
//xv2 := map[string]interface{}{"lru": &c.lru, "rce": &rce.inLRU}
//fmt.Printf("\n--------\n%s\n\n\n", pretty.Sprint(xv2))
c.size += δsize c.size += δsize
if c.size > c.sizeMax { if c.size > c.sizeMax {
gcrun = true gcrun = true
} }
c.gcMu.Unlock()
c.gcMu.Unlock()
if gcrun { if gcrun {
c.gcsignal() c.gcsignal()
} }
...@@ -495,7 +492,7 @@ func (c *Cache) gc() { ...@@ -495,7 +492,7 @@ func (c *Cache) gc() {
} }
// kill 1 least-used rce // kill 1 least-used rce
h := c.lru.next h := c.lru.Next()
if h == &c.lru { if h == &c.lru {
panic("cache: gc: empty .lru but .size > .sizeMax") panic("cache: gc: empty .lru but .size > .sizeMax")
} }
...@@ -608,8 +605,17 @@ func (rce *revCacheEntry) userErr(xid zodb.Xid) error { ...@@ -608,8 +605,17 @@ func (rce *revCacheEntry) userErr(xid zodb.Xid) error {
return rce.err return rce.err
} }
// list head that knows it is in revCacheEntry.inLRU
type lruHead struct {
list.Head
}
// XXX vvv strictly speaking -unsafe.Offsetof(h.Head)
func (h *lruHead) Next() *lruHead { return (*lruHead)(unsafe.Pointer(h.Head.Next())) }
func (h *lruHead) Prev() *lruHead { return (*lruHead)(unsafe.Pointer(h.Head.Prev())) }
// revCacheEntry: .inLRU -> . // revCacheEntry: .inLRU -> .
func (h *listHead) rceFromInLRU() (rce *revCacheEntry) { func (h *lruHead) rceFromInLRU() (rce *revCacheEntry) {
urce := unsafe.Pointer(uintptr(unsafe.Pointer(h)) - unsafe.Offsetof(rce.inLRU)) urce := unsafe.Pointer(uintptr(unsafe.Pointer(h)) - unsafe.Offsetof(rce.inLRU))
return (*revCacheEntry)(urce) return (*revCacheEntry)(urce)
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// See COPYING file for full licensing terms. // See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options. // See https://www.nexedi.com/licensing for rationale and options.
package client package storage
import ( import (
"bytes" "bytes"
...@@ -199,10 +199,10 @@ func TestCache(t *testing.T) { ...@@ -199,10 +199,10 @@ func TestCache(t *testing.T) {
t.Helper() t.Helper()
size := 0 size := 0
var mruv []*revCacheEntry var mruv []*revCacheEntry
for hp, h := &c.lru, c.lru.prev; h != &c.lru; hp, h = h, h.prev { for hp, h := &c.lru, c.lru.Prev(); h != &c.lru; hp, h = h, h.Prev() {
//xv := []interface{}{&c.lru, h.rceFromInLRU()} //xv := []interface{}{&c.lru, h.rceFromInLRU()}
//debug.Print(xv) // &c.lru, h.rceFromInLRU()) //debug.Print(xv) // &c.lru, h.rceFromInLRU())
if h.next != hp { if h.Next() != hp {
t.Fatalf("LRU list .next/.prev broken for\nh:\n%s\n\nhp:\n%s\n", t.Fatalf("LRU list .next/.prev broken for\nh:\n%s\n\nhp:\n%s\n",
debug.Sprint(h), debug.Sprint(hp)) debug.Sprint(h), debug.Sprint(hp))
} }
......
// Copyright (C) 2017 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
// Package storage provides common bits related to ZODB storages. XXX text
package storage
// Code generated by lab.nexedi.com/kirr/go123/tracing/cmd/gotrace; DO NOT EDIT. // Code generated by lab.nexedi.com/kirr/go123/tracing/cmd/gotrace; DO NOT EDIT.
package client package storage
// code generated for tracepoints // code generated for tracepoints
import ( import (
...@@ -63,4 +63,4 @@ func traceCacheGCStart_Attach(pg *tracing.ProbeGroup, probe func(c *Cache)) *tra ...@@ -63,4 +63,4 @@ func traceCacheGCStart_Attach(pg *tracing.ProbeGroup, probe func(c *Cache)) *tra
} }
// trace export signature // trace export signature
func _trace_exporthash_624de1c8d179b91f695f79fec7f7cdb7386501f4() {} func _trace_exporthash_46a80e8af5056736069c296a95ad4c94388ab850() {}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment