Commit e2d902b0 authored by Kirill Smelkov's avatar Kirill Smelkov

go/zodb: Expose access to connection's live cache as public API

Wendelin.core (wcfs) needs to check whether an object is currently
cached or not.
parent db852511
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
// Copyright (C) 2018-2019 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
......@@ -50,9 +50,22 @@ type Connection struct {
txn transaction.Transaction // opened under this txn; nil if idle in DB pool.
at Tid // current view of database; stable inside a transaction.
// {} oid -> obj
//
// rationale:
cache LiveCache // cache of connection's in-RAM objects
}
// LiveCache keeps registry of live in-RAM objects for a Connection.
//
// It semantically consists of
//
// {} oid -> obj
//
// but does not hold strong reference to cached objects.
//
// LiveCache is not safe to use from multiple goroutines simultaneously.
//
// Use .Lock() / .Unlock() to serialize access.
type LiveCache struct {
// rationale for using weakref:
//
// on invalidations: we need to go oid -> obj and invalidate it.
// -> Connection need to keep {} oid -> obj.
......@@ -98,11 +111,12 @@ type Connection struct {
//
// NOTE2 finalizers don't run on when they are attached to an object in cycle.
// Hopefully we don't have cycles with BTree/Bucket.
objmu sync.Mutex
sync.Mutex
objtab map[Oid]*weak.Ref // oid -> weak.Ref(IPersistent)
// hooks for application to influence live caching decisions.
cacheControl LiveCacheControl
control LiveCacheControl
}
// LiveCacheControl is the interface that allows applications to influence
......@@ -123,10 +137,12 @@ type LiveCacheControl interface {
// newConnection creates new Connection associated with db.
func newConnection(db *DB, at Tid) *Connection {
return &Connection{
stor: db.stor,
db: db,
at: at,
objtab: make(map[Oid]*weak.Ref),
stor: db.stor,
db: db,
at: at,
cache: LiveCache{
objtab: make(map[Oid]*weak.Ref),
},
}
}
......@@ -136,6 +152,11 @@ func (conn *Connection) At() Tid {
return conn.at
}
// Cache returns connection's cache of live objects.
func (conn *Connection) Cache() *LiveCache {
return &conn.cache
}
// wrongClassError is the error cause returned when ZODB object's class is not what was expected.
type wrongClassError struct {
want, have string
......@@ -145,27 +166,34 @@ func (e *wrongClassError) Error() string {
return fmt.Sprintf("wrong class: want %q; have %q", e.want, e.have)
}
// get is like Get, but used when we already know object class.
// Get lookups object corresponding to oid in the cache.
//
// Use-case: in ZODB references are (pyclass, oid), so new ghost is created
// without further loading anything.
func (conn *Connection) get(class string, oid Oid) (IPersistent, error) {
conn.objmu.Lock() // XXX -> rlock?
wobj := conn.objtab[oid]
// If object is found, it is guaranteed to stay in live cache while the caller keeps reference to it.
func (cache *LiveCache) Get(oid Oid) IPersistent {
wobj := cache.objtab[oid]
var obj IPersistent
checkClass := false
if wobj != nil {
if xobj := wobj.Get(); xobj != nil {
obj = xobj.(IPersistent)
}
}
return obj
}
// get is like Get, but used when we already know object class.
//
// Use-case: in ZODB references are (pyclass, oid), so new ghost is created
// without further loading anything.
func (conn *Connection) get(class string, oid Oid) (IPersistent, error) {
checkClass := true
conn.cache.Lock() // XXX -> rlock?
obj := conn.cache.Get(oid)
if obj == nil {
obj = newGhost(class, oid, conn)
conn.objtab[oid] = weak.NewRef(obj)
} else {
checkClass = true
conn.cache.objtab[oid] = weak.NewRef(obj)
checkClass = false
}
conn.objmu.Unlock()
conn.cache.Unlock()
if checkClass {
if cls := zclassOf(obj); class != cls {
......@@ -191,20 +219,16 @@ func (conn *Connection) Get(ctx context.Context, oid Oid) (_ IPersistent, err er
conn.checkTxnCtx(ctx, "Get")
defer xerr.Contextf(&err, "Get %s", oid)
conn.objmu.Lock() // XXX -> rlock?
wobj := conn.objtab[oid]
var xobj interface{}
if wobj != nil {
xobj = wobj.Get()
}
conn.objmu.Unlock()
conn.cache.Lock() // XXX -> rlock?
obj := conn.cache.Get(oid)
conn.cache.Unlock()
// object was already there in objtab.
if xobj != nil {
return xobj.(IPersistent), nil
// object was already there in cache.
if obj != nil {
return obj, nil
}
// object is not there in objtab - raw load it, get its class -> get(pyclass, oid)
// object is not in cache - raw load it, get its class -> get(pyclass, oid)
// XXX "py always" hardcoded
class, pystate, serial, err := conn.loadpy(ctx, oid)
if err != nil {
......@@ -212,7 +236,7 @@ func (conn *Connection) Get(ctx context.Context, oid Oid) (_ IPersistent, err er
return nil, err
}
obj, err := conn.get(class, oid)
obj, err = conn.get(class, oid)
if err != nil {
return nil, err
}
......
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
// Copyright (C) 2018-2019 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
......@@ -223,7 +223,7 @@ func (obj *Persistent) PDeactivate() {
// no constant load/unload on object access. XXX -> MRU cache?
// NOTE wcfs manages its objects explicitly and does not need this.
if cc := obj.jar.cacheControl; cc != nil {
if cc := obj.jar.cache.control; cc != nil {
if !cc.WantEvict(obj.instance) {
return
}
......
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