Logo ROOT  
Reference Guide
TMemoryRegulator.cxx
Go to the documentation of this file.
1// @(#)root/pyroot:$Id$
2// Author: Wim Lavrijsen, Apr 2004
3
4// Bindings
5#include "PyROOT.h"
6#include "TMemoryRegulator.h"
7#include "ObjectProxy.h"
8
9// Standard
10#include <assert.h>
11#include <string.h>
12#include <Riostream.h>
13
14
15//- static data --------------------------------------------------------------
18
19
20namespace {
21
22// memory regulater callback for deletion of registered objects
23 PyMethodDef gObjectEraseMethodDef = {
24 const_cast< char* >( "TMemoryRegulator_internal_ObjectEraseCallback" ),
26 METH_O,
27 NULL
28 };
29
30// pseudo-None type for masking out objects on the python side
31 PyTypeObject PyROOT_NoneType;
32
33////////////////////////////////////////////////////////////////////////////////
34
35 Py_ssize_t AlwaysNullLength( PyObject* )
36 {
37 return 0;
38 }
39
40////////////////////////////////////////////////////////////////////////////////
41
42 PyMappingMethods PyROOT_NoneType_mapping = {
43 AlwaysNullLength,
44 (binaryfunc) 0,
45 (objobjargproc) 0
46 };
47
48// silence warning about some cast operations
49#if defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || (__GNUC_MINOR__ >= 3)))) && !__INTEL_COMPILER
50#pragma GCC diagnostic ignored "-Wstrict-aliasing"
51#endif
52
53////////////////////////////////////////////////////////////////////////////////
54
55 struct InitPyROOT_NoneType_t {
56 InitPyROOT_NoneType_t()
57 {
58 // createa PyROOT NoneType (for references that went dodo) from NoneType
59 memset( &PyROOT_NoneType, 0, sizeof( PyROOT_NoneType ) );
60
61 ((PyObject&)PyROOT_NoneType).ob_type = &PyType_Type;
62 ((PyObject&)PyROOT_NoneType).ob_refcnt = 1;
63 ((PyVarObject&)PyROOT_NoneType).ob_size = 0;
64
65 PyROOT_NoneType.tp_name = const_cast< char* >( "PyROOT_NoneType" );
66 PyROOT_NoneType.tp_flags = Py_TPFLAGS_HAVE_RICHCOMPARE | Py_TPFLAGS_HAVE_GC;
67
68 PyROOT_NoneType.tp_traverse = (traverseproc) 0;
69 PyROOT_NoneType.tp_clear = (inquiry) 0;
70 PyROOT_NoneType.tp_dealloc = (destructor) &InitPyROOT_NoneType_t::DeAlloc;
71 PyROOT_NoneType.tp_repr = Py_TYPE(Py_None)->tp_repr;
72 PyROOT_NoneType.tp_richcompare = (richcmpfunc) &InitPyROOT_NoneType_t::RichCompare;
73#if PY_VERSION_HEX < 0x03000000
74// tp_compare has become tp_reserved (place holder only) in p3
75 PyROOT_NoneType.tp_compare = (cmpfunc) &InitPyROOT_NoneType_t::Compare;
76#endif
77 PyROOT_NoneType.tp_hash = (hashfunc) &InitPyROOT_NoneType_t::PtrHash;
78
79 PyROOT_NoneType.tp_as_mapping = &PyROOT_NoneType_mapping;
80
81 PyType_Ready( &PyROOT_NoneType );
82 }
83
84 static void DeAlloc( PyObject* obj ) { Py_TYPE(obj)->tp_free( obj ); }
85 static int PtrHash( PyObject* obj ) { return (int)Long_t(obj); }
86
87 static PyObject* RichCompare( PyObject*, PyObject* other, int opid )
88 {
89 return PyObject_RichCompare( other, Py_None, opid );
90 }
91
92 static int Compare( PyObject*, PyObject* other )
93 {
94#if PY_VERSION_HEX < 0x03000000
95 return PyObject_Compare( other, Py_None );
96#else
97// TODO the following isn't correct as it doens't order, but will do for now ...
98 return ! PyObject_RichCompareBool( other, Py_None, Py_EQ );
99#endif
100 }
101 };
102
103} // unnamed namespace
104
105
106//- ctor/dtor ----------------------------------------------------------------
108{
109// setup NoneType for referencing and create weakref cache
110 static InitPyROOT_NoneType_t initPyROOT_NoneType;
111
112 assert( fgObjectTable == 0 );
114
115 assert( fgWeakRefTable == 0 );
117}
118
119////////////////////////////////////////////////////////////////////////////////
120/// cleanup weakref cache
121
123{
124 delete fgWeakRefTable;
125 fgWeakRefTable = 0;
126
127 delete fgObjectTable;
128 fgObjectTable = 0;
129}
130
131
132//- public members -----------------------------------------------------------
134{
135// called whenever a TObject gets destroyed
136 if ( ! object || ! fgObjectTable ) // table can be deleted before libCore is done
137 return;
138
139// see whether we're tracking this object
140 ObjectMap_t::iterator ppo = fgObjectTable->find( object );
141
142 if ( ppo != fgObjectTable->end() ) {
143 fgWeakRefTable->erase( fgWeakRefTable->find( ppo->second ) );
144
145 // get the tracked object
146 ObjectProxy* pyobj = (ObjectProxy*)PyWeakref_GetObject( ppo->second );
147 if ( ! pyobj ) {
148 fgObjectTable->erase( ppo );
149 return;
150 }
151
152 // clean up the weak reference.
153 Py_DECREF( ppo->second );
154
155 // nullify the object
156 if ( ObjectProxy_Check( pyobj ) ) {
157 if ( ! PyROOT_NoneType.tp_traverse ) {
158 // take a reference as we're copying its function pointers
159 Py_INCREF( Py_TYPE(pyobj) );
160
161 // all object that arrive here are expected to be of the same type ("instance")
162 PyROOT_NoneType.tp_traverse = Py_TYPE(pyobj)->tp_traverse;
163 PyROOT_NoneType.tp_clear = Py_TYPE(pyobj)->tp_clear;
164 PyROOT_NoneType.tp_free = Py_TYPE(pyobj)->tp_free;
165 } else if ( PyROOT_NoneType.tp_traverse != Py_TYPE(pyobj)->tp_traverse ) {
166 std::cerr << "in PyROOT::TMemoryRegulater, unexpected object of type: "
167 << Py_TYPE(pyobj)->tp_name << std::endl;
168
169 // leave before too much damage is done
170 return;
171 }
172
173 // notify any other weak referents by playing dead
174 int refcnt = ((PyObject*)pyobj)->ob_refcnt;
175 ((PyObject*)pyobj)->ob_refcnt = 0;
176 PyObject_ClearWeakRefs( (PyObject*)pyobj );
177 ((PyObject*)pyobj)->ob_refcnt = refcnt;
178
179 // cleanup object internals
180 pyobj->Release(); // held object is out of scope now anyway
181 op_dealloc_nofree( pyobj ); // normal object cleanup, while keeping memory
182
183 // reset type object
184 Py_INCREF( (PyObject*)(void*)&PyROOT_NoneType );
185 Py_DECREF( Py_TYPE(pyobj) );
186 ((PyObject*)pyobj)->ob_type = &PyROOT_NoneType;
187 }
188
189 // erase the object from tracking (weakref table already cleared, above)
190 fgObjectTable->erase( ppo );
191 }
192}
193
194////////////////////////////////////////////////////////////////////////////////
195/// clean up all tracked objects
196
198{
199 while (!fgObjectTable->empty()) {
200 auto elem = fgObjectTable->begin();
201 auto cppobj = elem->first;
202 auto pyobj = (ObjectProxy*)PyWeakref_GetObject(elem->second);
203
204 if (pyobj && (pyobj->fFlags & ObjectProxy::kIsOwner)) {
205 // Only delete the C++ object if the Python proxy owns it.
206 // The deletion will trigger RecursiveRemove on the object
207 delete cppobj;
208 }
209 else {
210 // Non-owning proxy, just unregister to clean tables.
211 // The proxy deletion by Python will have no effect on C++, so all good
212 UnregisterObject(cppobj);
213 }
214 }
215}
216
217////////////////////////////////////////////////////////////////////////////////
218/// start tracking <object> proxied by <pyobj>
219
221{
222 static PyObject* objectEraseCallback = PyCFunction_New(&gObjectEraseMethodDef, nullptr);
223
224 if ( ! ( pyobj && object ) )
225 return kFALSE;
226
227 ObjectMap_t::iterator ppo = fgObjectTable->find( object );
228 if ( ppo == fgObjectTable->end() ) {
229 object->SetBit( TObject::kMustCleanup );
230 PyObject* pyref = PyWeakref_NewRef( (PyObject*)pyobj, objectEraseCallback );
231 ObjectMap_t::iterator newppo = fgObjectTable->insert( std::make_pair( object, pyref ) ).first;
232 (*fgWeakRefTable)[ pyref ] = newppo; // no Py_INCREF on pyref, as object table has one
233 return kTRUE;
234 }
235
236 return kFALSE;
237}
238
239////////////////////////////////////////////////////////////////////////////////
240/// stop tracking <object>, without notification
241
243{
244 ObjectMap_t::iterator ppo = fgObjectTable->find( object );
245
246 if ( ppo != fgObjectTable->end() ) {
247 fgWeakRefTable->erase( fgWeakRefTable->find( ppo->second ) );
248 fgObjectTable->erase( ppo );
249 return kTRUE;
250 }
251
252 return kFALSE;
253}
254
255////////////////////////////////////////////////////////////////////////////////
256/// lookup <object>, return old proxy if tracked
257
259{
260 if ( ! object )
261 return 0;
262
263 ObjectMap_t::iterator ppo = fgObjectTable->find( object );
264 if ( ppo != fgObjectTable->end() ) {
265 PyObject* pyobj = PyWeakref_GetObject( ppo->second );
266 Py_XINCREF( pyobj );
267 if ( pyobj && ((ObjectProxy*)pyobj)->ObjectIsA() != klass ) {
268 Py_DECREF( pyobj );
269 return 0;
270 }
271 return pyobj;
272 }
273
274 return 0;
275}
276
277
278//- private static members ------------------------------------------------------
280{
281// called when one of the python objects we've registered is going away
282 ObjectProxy* pyobj = (ObjectProxy*)PyWeakref_GetObject( pyref );
283
284 if ( ObjectProxy_Check( pyobj ) && pyobj->GetObject() != 0 ) {
285 // get TObject pointer to the object
286 static Cppyy::TCppScope_t sTObjectScope = Cppyy::GetScope( "TObject" );
287 Cppyy::TCppType_t klass = pyobj->ObjectIsA();
288 if ( Cppyy::IsSubtype( klass, sTObjectScope) ) {
289 void* address = pyobj->GetObject();
290 TObject* object = (TObject*)((Long_t)address + \
291 Cppyy::GetBaseOffset( klass, sTObjectScope, address, 1 /* up-cast */ ) );
292
293 // erase if tracked
294 ObjectMap_t::iterator ppo = fgObjectTable->find( object );
295 if ( ppo != fgObjectTable->end() ) {
296 // cleanup table entries and weak reference
297 fgWeakRefTable->erase( fgWeakRefTable->find( ppo->second ) );
298 Py_DECREF( ppo->second );
299 fgObjectTable->erase( ppo );
300 }
301 }
302 } else {
303 // object already dead; need to clean up the weak ref from the table
304 WeakRefMap_t::iterator wri = fgWeakRefTable->find( pyref );
305 if ( wri != fgWeakRefTable->end() ) {
306 fgObjectTable->erase( wri->second );
307 fgWeakRefTable->erase( wri );
308 Py_DECREF( pyref );
309 }
310 }
311
312 Py_INCREF( Py_None );
313 return Py_None;
314}
#define Py_TYPE(ob)
Definition: PyROOT.h:166
int Py_ssize_t
Definition: PyROOT.h:171
const Bool_t kFALSE
Definition: RtypesCore.h:88
long Long_t
Definition: RtypesCore.h:50
bool Bool_t
Definition: RtypesCore.h:59
const Bool_t kTRUE
Definition: RtypesCore.h:87
Int_t Compare(const void *item1, const void *item2)
_object PyObject
Definition: TPyArg.h:20
Cppyy::TCppType_t ObjectIsA() const
Definition: ObjectProxy.h:66
void * GetObject() const
Definition: ObjectProxy.h:47
static Bool_t UnregisterObject(TObject *object)
stop tracking <object>, without notification
static Bool_t RegisterObject(ObjectProxy *pyobj, TObject *object)
start tracking <object> proxied by <pyobj>
virtual void RecursiveRemove(TObject *object)
Recursively remove this object from a list.
~TMemoryRegulator()
cleanup weakref cache
std::unordered_map< TObject *, PyObject * > ObjectMap_t
static ObjectMap_t * fgObjectTable
static PyObject * ObjectEraseCallback(PyObject *, PyObject *pyref)
void ClearProxiedObjects()
clean up all tracked objects
std::unordered_map< PyObject *, ObjectMap_t::iterator > WeakRefMap_t
static PyObject * RetrieveObject(TObject *object, Cppyy::TCppType_t klass)
lookup <object>, return old proxy if tracked
static WeakRefMap_t * fgWeakRefTable
Mother of all ROOT objects.
Definition: TObject.h:37
@ kMustCleanup
if object destructor must call RecursiveRemove()
Definition: TObject.h:60
ptrdiff_t GetBaseOffset(TCppType_t derived, TCppType_t base, TCppObject_t address, int direction, bool rerror=false)
Definition: Cppyy.cxx:644
ptrdiff_t TCppScope_t
Definition: Cppyy.h:15
TCppScope_t TCppType_t
Definition: Cppyy.h:16
TCppScope_t GetScope(const std::string &scope_name)
Definition: Cppyy.cxx:197
Bool_t IsSubtype(TCppType_t derived, TCppType_t base)
Definition: Cppyy.cxx:622
Bool_t ObjectProxy_Check(T *object)
Definition: ObjectProxy.h:91
void op_dealloc_nofree(ObjectProxy *)
Destroy the held C++ object, if owned; does not deallocate the proxy.
Definition: ObjectProxy.cxx:46