Salome HOME
Avoid additionnal CORBA invocation to clear context
[modules/kernel.git] / src / Container / SALOME_PyNode.py
index 0238ad9f19b65735fade4ed0829205f35d185ed6..dfc5467e88b4886dc2371b7e5c301a719e372d6f 100644 (file)
@@ -1,5 +1,5 @@
 #  -*- coding: iso-8859-1 -*-
-# Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
+# Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -30,6 +30,8 @@ import Engines__POA
 import SALOME__POA
 import SALOME
 
+MY_CONTAINER_ENTRY_IN_GLBS = "my_container"
+
 class Generic(SALOME__POA.GenericObj):
   """A Python implementation of the GenericObj CORBA IDL"""
   def __init__(self,poa):
@@ -66,7 +68,7 @@ class PyNode_i (Engines__POA.PyNode,Generic):
     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
     ccode=compile(code,nodeName,'exec')
     self.context={}
-    self.context["my_container"] = self.my_container
+    self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
     exec(ccode, self.context)
 
   def getContainer(self):
@@ -121,6 +123,43 @@ SALOME_BIG_OBJ_ON_DISK_THRES_VAR = "SALOME_BIG_OBJ_ON_DISK_THRES"
 # default is 50 MB
 SALOME_BIG_OBJ_ON_DISK_THRES_DFT = 50000000
 
+from ctypes import c_int
+TypeCounter = c_int
+
+def GetSizeOfTCnt():
+  return len( bytes(TypeCounter(0) ) )
+
+def GetObjectFromFile(fname):
+  with open(fname,"rb") as f:
+    cntb = f.read( GetSizeOfTCnt() )
+    cnt = TypeCounter.from_buffer_copy( cntb ).value
+    obj = pickle.load(f)
+  return obj,cnt
+
+def DumpInFile(obj,fname):
+  with open(fname,"wb") as f:
+    f.write( bytes( TypeCounter(1) ) )
+    f.write( obj )
+
+def IncrRefInFile(fname):
+  with open(fname,"rb") as f:
+    cntb = f.read( GetSizeOfTCnt() )
+  cnt = TypeCounter.from_buffer_copy( cntb ).value
+  with open(fname,"rb+") as f:
+    f.write( bytes( TypeCounter(cnt+1) ) )
+
+def DecrRefInFile(fname):
+  import os
+  with open(fname,"rb") as f:
+    cntb = f.read( GetSizeOfTCnt() )
+  cnt = TypeCounter.from_buffer_copy( cntb ).value
+  #
+  if cnt == 1:
+    os.unlink( fname )
+  else:
+    with open(fname,"rb+") as f:
+        f.write( bytes( TypeCounter(cnt-1) ) )
+
 def GetBigObjectOnDiskThreshold():
   import os
   if SALOME_BIG_OBJ_ON_DISK_THRES_VAR in os.environ:
@@ -132,7 +171,7 @@ def GetBigObjectDirectory():
   import os
   if SALOME_FILE_BIG_OBJ_DIR not in os.environ:
     raise RuntimeError("An object of size higher than limit detected and no directory specified to dump it in file !")
-  return os.path.expandvars( os.path.expandvars( os.environ[SALOME_FILE_BIG_OBJ_DIR] ) )
+  return os.path.expanduser( os.path.expandvars( os.environ[SALOME_FILE_BIG_OBJ_DIR] ) )
 
 def GetBigObjectFileName():
   """
@@ -151,12 +190,29 @@ class BigObjectOnDiskBase:
     :type objSerialized: bytes
     """
     self._filename = fileName
+    # attribute _destroy is here to tell client side or server side
+    # only client side can be with _destroy set to True. server side due to risk of concurrency
+    # so pickled form of self must be done with this attribute set to False.
     self._destroy = False
     self.__dumpIntoFile(objSerialized)
 
   def getDestroyStatus(self):
     return self._destroy
 
+  def incrRef(self):
+    if self._destroy:
+      IncrRefInFile( self._filename )
+    else:
+      # should never happen !
+      RuntimeError("Invalid call to incrRef !")
+
+  def decrRef(self):
+    if self._destroy:
+      DecrRefInFile( self._filename )
+    else:
+      # should never happen !
+      RuntimeError("Invalid call to decrRef !")
+
   def unlinkOnDestructor(self):
     self._destroy = True
 
@@ -168,20 +224,30 @@ class BigObjectOnDiskBase:
 
   def __del__(self):
     if self._destroy:
-      import os
-      os.unlink( self._filename )
+      DecrRefInFile( self._filename )
 
   def getFileName(self):
     return self._filename
   
   def __dumpIntoFile(self, objSerialized):
-    with open(self._filename,"wb") as f:
-      f.write(objSerialized)
+    DumpInFile( objSerialized, self._filename )
 
   def get(self):
-    import pickle
-    with open(self._filename,"rb") as f:
-      return pickle.load(f)
+    obj, _ = GetObjectFromFile( self._filename )
+    return obj
+
+  def __float__(self):
+    return float( self.get() )
+    
+  def __int__(self):
+    return int( self.get() )
+    
+  def __str__(self):
+    obj = self.get()
+    if isinstance(obj,str):
+        return obj
+    else:
+        raise RuntimeError("Not a string")
       
 class BigObjectOnDisk(BigObjectOnDiskBase):
   def __init__(self, fileName, objSerialized):
@@ -198,6 +264,12 @@ class BigObjectOnDiskListElement(BigObjectOnDiskBase):
     fullObj = BigObjectOnDiskBase.get(self)
     return fullObj[ self._pos ]
     
+  def __getitem__(self, i):
+    return self.get()[i]
+
+  def __len__(self):
+    return len(self.get())
+    
 class BigObjectOnDiskSequence(BigObjectOnDiskBase):
   def __init__(self, length, fileName, objSerialized):
     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
@@ -232,15 +304,33 @@ def SpoolPickleObject( obj ):
     pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
     return pickleProxy
 
-def UnProxyObject( obj ):
+def UnProxyObjectSimple( obj ):
+  """
+  Method to be called in Remote mode. Alterate the obj _status attribute. 
+  Because the slave process does not participate in the reference counting
+  """
   if isinstance(obj,BigObjectOnDiskBase):
     obj.doNotTouchFile()
     return obj.get()
-  if isinstance(obj,list) or isinstance(obj,tuple):
+  elif isinstance( obj, list):
+    retObj = []
     for elt in obj:
-      if isinstance(elt,BigObjectOnDiskBase):
-        elt.doNotTouchFile()
+      retObj.append( UnProxyObjectSimple(elt) )
+    return retObj
+  else:
     return obj
+
+def UnProxyObjectSimpleLocal( obj ):
+  """
+  Method to be called in Local mode. Do not alterate the PyObj counter
+  """
+  if isinstance(obj,BigObjectOnDiskBase):
+    return obj.get()
+  elif isinstance( obj, list):
+    retObj = []
+    for elt in obj:
+      retObj.append( UnProxyObjectSimpleLocal(elt) )
+    return retObj
   else:
     return obj
     
@@ -284,7 +374,12 @@ class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
     self.ccode=compile(code,nodeName,'exec')
     self.context={}
-    self.context["my_container"] = self.my_container
+    self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
+    
+  def __del__(self):
+    # force removal of self.context. Don t know why it s not done by default
+    self.removeAllVarsInContext()
+    pass
 
   def getContainer(self):
     return self.my_container
@@ -343,7 +438,7 @@ class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
       _,kws=pickle.loads(data)
       for elt in kws:
         # fetch real data if necessary
-        kws[elt] = UnProxyObject( kws[elt] )
+        kws[elt] = UnProxyObjectSimple( kws[elt] )
       self.context.update(kws)
     except Exception:
       exc_typ,exc_val,exc_fr=sys.exc_info()
@@ -376,7 +471,7 @@ class PyScriptNode_i (Engines__POA.PyScriptNode,Generic):
   def listAllVarsInContext(self):
       import re
       pat = re.compile("^__([a-z]+)__$")
-      return [elt for elt in self.context if not pat.match(elt)]
+      return [elt for elt in self.context if not pat.match(elt) and elt != MY_CONTAINER_ENTRY_IN_GLBS]
       
   def removeAllVarsInContext(self):
       for elt in self.listAllVarsInContext():