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