Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
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 --------------------------------------------------------------
16 PyROOT::TMemoryRegulator::ObjectMap_t* PyROOT::TMemoryRegulator::fgObjectTable = 0;
17 PyROOT::TMemoryRegulator::WeakRefMap_t* PyROOT::TMemoryRegulator::fgWeakRefTable = 0;
18 
19 
20 namespace {
21 
22 // memory regulater callback for deletion of registered objects
23  PyMethodDef gObjectEraseMethodDef = {
24  const_cast< char* >( "TMemoryRegulator_internal_ObjectEraseCallback" ),
25  (PyCFunction) PyROOT::TMemoryRegulator::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 ----------------------------------------------------------------
107 PyROOT::TMemoryRegulator::TMemoryRegulator()
108 {
109 // setup NoneType for referencing and create weakref cache
110  static InitPyROOT_NoneType_t initPyROOT_NoneType;
111 
112  assert( fgObjectTable == 0 );
113  fgObjectTable = new ObjectMap_t;
114 
115  assert( fgWeakRefTable == 0 );
116  fgWeakRefTable = new WeakRefMap_t;
117 }
118 
119 ////////////////////////////////////////////////////////////////////////////////
120 /// cleanup weakref cache
121 
122 PyROOT::TMemoryRegulator::~TMemoryRegulator()
123 {
124  delete fgWeakRefTable;
125  fgWeakRefTable = 0;
126 
127  delete fgObjectTable;
128  fgObjectTable = 0;
129 }
130 
131 
132 //- public members -----------------------------------------------------------
133 void PyROOT::TMemoryRegulator::RecursiveRemove( TObject* object )
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 
197 void PyROOT::TMemoryRegulator::ClearProxiedObjects()
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 
220 Bool_t PyROOT::TMemoryRegulator::RegisterObject( ObjectProxy* pyobj, TObject* object )
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 
242 Bool_t PyROOT::TMemoryRegulator::UnregisterObject( TObject* object )
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 
258 PyObject* PyROOT::TMemoryRegulator::RetrieveObject( TObject* object, Cppyy::TCppType_t klass )
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 ------------------------------------------------------
279 PyObject* PyROOT::TMemoryRegulator::ObjectEraseCallback( PyObject*, PyObject* pyref )
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 }