Commit 6fbf83f5 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 7bb6a8a8
......@@ -87,19 +87,6 @@ type Object interface {
PInvalidate()
}
// PyObject is the interface that every in-RAM object representing Python ZODB object implements.
type PyObject interface {
Object
PyClass() pickle.Class // python class of this object
// PyState() interface{} // object state. python passes this to pyclass.__new__().__setstate__()
// XXX want to hide from PyObject. Rationale: we do not want e.g. PySetState to
// be available to user who holds PyObject interface: it is confusing to have
// both PActivate and PySetState at the same time.
// PyStateful
}
// ObjectState describe state of in-RAM object.
type ObjectState int
......@@ -127,15 +114,6 @@ func (obj *object) PJar() *Connection { return obj.jar }
func (obj *object) POid() zodb.Oid { return obj.oid }
func (obj *object) PSerial() zodb.Tid { return obj.serial }
// pyObject is common base implementation for in-RAM representation of ZODB Python objects.
type pyObject struct {
object
pyclass pickle.Class
}
func (pyobj *pyObject) PyClass() pickle.Class { return pyobj.pyclass }
//func (pyobj *pyObject) PyState() interface{} { return pyobj.pystate }
// loadState indicates object's load state/result.
//
// when !ready the loading is in progress.
......@@ -164,20 +142,6 @@ type Stateful interface {
//GetState() *mem.Buf
}
// PyStateful is the interface describing in-RAM object whose data state can be
// exchanged as Python data.
type PyStateful interface {
//Stateful XXX no need here?
// PySetState should set state of the in-RAM object from Python data.
// Analog of __setstate__() in Python.
PySetState(pystate interface{}) error
// PyGetState should return state of the in-RAM object as Python data.
// Analog of __getstate__() in Python.
//PyGetState() interface{} XXX
}
// Connection represents a view of ZODB database.
//
......@@ -241,7 +205,7 @@ 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 ZBtree/ZBucket XXX verify this
objmu sync.Mutex
objtab map[zodb.Oid]*WeakRef // oid -> WeakRef(PyObject)
objtab map[zodb.Oid]*WeakRef // oid -> WeakRef(Object)
// hooks for application to influence live caching decisions.
......@@ -257,182 +221,6 @@ type LiveCacheControl interface {
}
// ----------------------------------------
// Get returns in-RAM object corresponding to specified ZODB object according to current database view.
//
// If there is already in-RAM object that corresponds to oid, that in-RAM object is returned.
// Otherwise new in-RAM object is created and filled with object's class loaded from the database.
//
// The scope of the object returned is the Connection. XXX ok?
//
// The object's data is not neccessarily loaded after Get returns. Use
// PActivate to make sure the object ifs fully loaded.
func (conn *Connection) Get(ctx context.Context, oid zodb.Oid) (PyObject, error) {
conn.objmu.Lock() // XXX -> rlock
wobj := conn.objtab[oid]
var xobj interface{}
if wobj != nil {
xobj = wobj.Get()
}
conn.objmu.Unlock()
// object was already there in objtab.
if xobj != nil {
return xobj.(PyObject), nil
}
// object is not there in objtab - raw load it, get its class -> get(pyclass, oid)
pyclass, pystate, serial, err := conn.loadpy(ctx, oid)
if err != nil {
return nil, err // XXX errctx
}
obj, err := conn.get(pyclass, oid)
if err != nil {
return nil, err
}
// XXX we are dropping just loaded pystate. Usually Get should be used
// to only load root object, so maybe that is ok.
//
// TODO -> use (pystate, serial) to activate.
_, _ = pystate, serial
return obj, nil
}
// wrongClassError is the error cause returned when object's class is not what was expected.
type wrongClassError struct {
want, have pickle.Class
}
func (e *wrongClassError) Error() string {
return fmt.Sprintf("wrong class: want %q; have %q", e.want, e.have)
}
// get returns in-RAM object corresponding to specified ZODB object according to current database view.
//
// If there is already in-RAM object that corresponds to oid, that in-RAM object is returned.
// Otherwise new in-RAM object is created according to specified class.
//
// The object's data is not neccessarily loaded after get returns. Use
// PActivate to make sure the object is fully loaded.
//
// XXX object scope.
//
// Use-case: in ZODB references are (pyclass, oid), so new ghost is created
// without further loading anything.
func (conn *Connection) get(pyclass pickle.Class, oid zodb.Oid) (PyObject, error) {
conn.objmu.Lock() // XXX -> rlock
wobj := conn.objtab[oid]
var pyobj PyObject
checkClass := false
if wobj != nil {
if xobj := wobj.Get(); xobj != nil {
pyobj = xobj.(PyObject)
}
}
if pyobj == nil {
pyobj = conn.newGhost(pyclass, oid)
conn.objtab[oid] = NewWeakRef(pyobj)
} else {
checkClass = true
}
conn.objmu.Unlock()
if checkClass {
if cls := pyobj.PyClass(); pyclass != cls {
return nil, &zodb.OpError{
URL: conn.stor.URL(),
Op: fmt.Sprintf("@%s: get", conn.at), // XXX abuse
Args: oid,
Err: &wrongClassError{pyclass, cls},
}
}
}
return pyobj, nil
}
// load loads object specified by oid.
//
// XXX must be called ... (XXX e.g. outside transaction boundary) so that there is no race on .at .
func (conn *Connection) load(ctx context.Context, oid zodb.Oid) (_ *mem.Buf, serial zodb.Tid, _ error) {
return conn.stor.Load(ctx, zodb.Xid{Oid: oid, At: conn.at})
}
// loadpy loads object specified by oid and decodes it as a ZODB Python object.
//
// loadpy does not create any in-RAM object associated with Connection.
// It only returns decoded database data.
func (conn *Connection) loadpy(ctx context.Context, oid zodb.Oid) (pyclass pickle.Class, pystate interface{}, serial zodb.Tid, _ error) {
buf, serial, err := conn.stor.Load(ctx, zodb.Xid{Oid: oid, At: conn.at})
if err != nil {
return pickle.Class{}, nil, 0, err
}
defer buf.Release()
pyclass, pystate, err = zodb.PyData(buf.Data).Decode()
if err != nil {
return pickle.Class{}, nil, 0, err // XXX err ctx
}
return pyclass, pystate, serial, nil
}
// ---- pyclass -> new ghost ----
// path(pyclass) -> new(pyobj)
var classTab = make(map[string]func(base *pyObject)PyObject)
// registerPyClass registers python class to be transformed to Go instance
// created via classNew.
//
// must be called from global init().
func registerPyClass(pyClassPath string, classNew func(base *pyObject)PyObject) {
classTab[pyClassPath] = classNew
}
// newGhost creates new ghost object corresponding to pyclass and oid.
func (conn *Connection) newGhost(pyclass pickle.Class, oid zodb.Oid) PyObject {
pyobj := &pyObject{
object: object{jar: conn, oid: oid, serial: 0, state: GHOST},
pyclass: pyclass,
}
// switch on pyclass and transform e.g. "zodb.BTree.Bucket" -> *ZBucket
classNew := classTab[pyclass.Module + "." + pyclass.Name]
var instance PyObject
if classNew != nil {
instance = classNew(pyobj)
} else {
instance = &dummyPyInstance{pyObject: pyobj}
}
pyobj.instance = instance
return instance
}
// dummyPyInstance is used for python classes that were not registered.
type dummyPyInstance struct {
*pyObject
pystate interface{}
}
func (d *dummyPyInstance) DropState() {
d.pystate = nil
}
func (d *dummyPyInstance) PySetState(pystate interface{}) error {
d.pystate = pystate
return nil
}
// ---- activate/deactivate/invalidate ----
// PActivate implements Object.
......@@ -547,3 +335,16 @@ func (obj *object) PInvalidate() {
obj.state = GHOST
obj.loading = nil
}
// ----------------------------------------
// XXX Connection.{Get,get} without py dependency?
// but then how to create a ghost of correct class? -> reflect.Type?
// load loads object specified by oid.
//
// XXX must be called ... (XXX e.g. outside transaction boundary) so that there is no race on .at .
func (conn *Connection) load(ctx context.Context, oid zodb.Oid) (_ *mem.Buf, serial zodb.Tid, _ error) {
return conn.stor.Load(ctx, zodb.Xid{Oid: oid, At: conn.at})
}
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This software is subject to the provisions of the Zope Public License,
// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE
package main
// Bits that should be in ZODB XXX -> zodb
// PyObject is the interface that every in-RAM object representing Python ZODB object implements.
type PyObject interface {
Object
PyClass() pickle.Class // python class of this object
// PyState() interface{} // object state. python passes this to pyclass.__new__().__setstate__()
// XXX want to hide from PyObject. Rationale: we do not want e.g. PySetState to
// be available to user who holds PyObject interface: it is confusing to have
// both PActivate and PySetState at the same time.
// PyStateful
}
// pyObject is common base implementation for in-RAM representation of ZODB Python objects.
type pyObject struct {
object
pyclass pickle.Class
}
func (pyobj *pyObject) PyClass() pickle.Class { return pyobj.pyclass }
//func (pyobj *pyObject) PyState() interface{} { return pyobj.pystate }
// PyStateful is the interface describing in-RAM object whose data state can be
// exchanged as Python data.
type PyStateful interface {
//Stateful XXX no need here?
// PySetState should set state of the in-RAM object from Python data.
// Analog of __setstate__() in Python.
PySetState(pystate interface{}) error
// PyGetState should return state of the in-RAM object as Python data.
// Analog of __getstate__() in Python.
//PyGetState() interface{} XXX
}
// ---- pyclass -> new ghost ----
// path(pyclass) -> new(pyobj)
var classTab = make(map[string]func(base *pyObject)PyObject)
// registerPyClass registers python class to be transformed to Go instance
// created via classNew.
//
// must be called from global init().
func registerPyClass(pyClassPath string, classNew func(base *pyObject)PyObject) {
classTab[pyClassPath] = classNew
}
// newGhost creates new ghost object corresponding to pyclass and oid.
func (conn *Connection) newGhost(pyclass pickle.Class, oid zodb.Oid) PyObject {
pyobj := &pyObject{
object: object{jar: conn, oid: oid, serial: 0, state: GHOST},
pyclass: pyclass,
}
// switch on pyclass and transform e.g. "zodb.BTree.Bucket" -> *ZBucket
classNew := classTab[pyclass.Module + "." + pyclass.Name]
var instance PyObject
if classNew != nil {
instance = classNew(pyobj)
} else {
instance = &dummyPyInstance{pyObject: pyobj}
}
pyobj.instance = instance
return instance
}
// dummyPyInstance is used for python classes that were not registered.
type dummyPyInstance struct {
*pyObject
pystate interface{}
}
func (d *dummyPyInstance) DropState() {
d.pystate = nil
}
func (d *dummyPyInstance) PySetState(pystate interface{}) error {
d.pystate = pystate
return nil
}
// ----------------------------------------
// Get returns in-RAM object corresponding to specified ZODB object according to current database view.
//
// If there is already in-RAM object that corresponds to oid, that in-RAM object is returned.
// Otherwise new in-RAM object is created and filled with object's class loaded from the database.
//
// The scope of the object returned is the Connection. XXX ok?
//
// The object's data is not neccessarily loaded after Get returns. Use
// PActivate to make sure the object ifs fully loaded.
func (conn *Connection) Get(ctx context.Context, oid zodb.Oid) (PyObject, error) {
conn.objmu.Lock() // XXX -> rlock
wobj := conn.objtab[oid]
var xobj interface{}
if wobj != nil {
xobj = wobj.Get()
}
conn.objmu.Unlock()
// object was already there in objtab.
if xobj != nil {
return xobj.(PyObject), nil
}
// object is not there in objtab - raw load it, get its class -> get(pyclass, oid)
pyclass, pystate, serial, err := conn.loadpy(ctx, oid)
if err != nil {
return nil, err // XXX errctx
}
obj, err := conn.get(pyclass, oid)
if err != nil {
return nil, err
}
// XXX we are dropping just loaded pystate. Usually Get should be used
// to only load root object, so maybe that is ok.
//
// TODO -> use (pystate, serial) to activate.
_, _ = pystate, serial
return obj, nil
}
// wrongClassError is the error cause returned when python object's class is not what was expected.
type wrongClassError struct {
want, have pickle.Class
}
func (e *wrongClassError) Error() string {
return fmt.Sprintf("wrong class: want %q; have %q", e.want, e.have)
}
// get returns in-RAM object corresponding to specified ZODB object according to current database view.
//
// If there is already in-RAM object that corresponds to oid, that in-RAM object is returned.
// Otherwise new in-RAM object is created according to specified class.
//
// The object's data is not neccessarily loaded after get returns. Use
// PActivate to make sure the object is fully loaded.
//
// XXX object scope.
//
// Use-case: in ZODB references are (pyclass, oid), so new ghost is created
// without further loading anything.
func (conn *Connection) get(pyclass pickle.Class, oid zodb.Oid) (PyObject, error) {
conn.objmu.Lock() // XXX -> rlock
wobj := conn.objtab[oid]
var pyobj PyObject
checkClass := false
if wobj != nil {
if xobj := wobj.Get(); xobj != nil {
pyobj = xobj.(PyObject)
}
}
if pyobj == nil {
pyobj = conn.newGhost(pyclass, oid)
conn.objtab[oid] = NewWeakRef(pyobj)
} else {
checkClass = true
}
conn.objmu.Unlock()
if checkClass {
if cls := pyobj.PyClass(); pyclass != cls {
return nil, &zodb.OpError{
URL: conn.stor.URL(),
Op: fmt.Sprintf("@%s: get", conn.at), // XXX abuse
Args: oid,
Err: &wrongClassError{pyclass, cls},
}
}
}
return pyobj, nil
}
// loadpy loads object specified by oid and decodes it as a ZODB Python object.
//
// loadpy does not create any in-RAM object associated with Connection.
// It only returns decoded database data.
func (conn *Connection) loadpy(ctx context.Context, oid zodb.Oid) (pyclass pickle.Class, pystate interface{}, serial zodb.Tid, _ error) {
buf, serial, err := conn.stor.Load(ctx, zodb.Xid{Oid: oid, At: conn.at})
if err != nil {
return pickle.Class{}, nil, 0, err
}
defer buf.Release()
pyclass, pystate, err = zodb.PyData(buf.Data).Decode()
if err != nil {
return pickle.Class{}, nil, 0, err // XXX err ctx
}
return pyclass, pystate, serial, nil
}
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