]> SALOME platform Git repositories - modules/kernel.git/blob - src/Container/SALOME_PyNode.py
Salome HOME
[EDF30062] [EDF29150]: Additional fault tolerant mecanism
[modules/kernel.git] / src / Container / SALOME_PyNode.py
1 #  -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 #  File   : SALOME_PyNode.py
22 #  Author : Christian CAREMOLI, EDF
23 #  Module : SALOME
24 #  $Header$
25 #
26 import sys,traceback
27 import linecache
28 import pickle
29 import Engines__POA
30 import SALOME__POA
31 import SALOME
32 import logging
33 import KernelBasis
34 import abc
35 import os
36 import sys
37 from SALOME_ContainerHelper import ScriptExecInfo
38
39 MY_CONTAINER_ENTRY_IN_GLBS = "my_container"
40
41 MY_PERFORMANCE_LOG_ENTRY_IN_GLBS = "my_log_4_this_session"
42
43 MY_KEY_TO_DETECT_FINISH = "neib av tuot"
44
45 class Generic(SALOME__POA.GenericObj):
46   """A Python implementation of the GenericObj CORBA IDL"""
47   def __init__(self,poa):
48     self.poa=poa
49     self.cnt=1
50
51   def Register(self):
52     #print("Register called : %d"%self.cnt)
53     self.cnt+=1
54
55   def UnRegister(self):
56     #print("UnRegister called : %d"%self.cnt)
57     self.cnt-=1
58     if self.cnt <= 0:
59       oid=self.poa.servant_to_id(self)
60       self.poa.deactivate_object(oid)
61
62   def Destroy(self):
63     print("WARNING SALOME::GenericObj::Destroy() function is obsolete! Use UnRegister() instead.")
64     self.UnRegister()
65
66   def __del__(self):
67     #print("Destuctor called")
68     pass
69
70 class PyNode_i (Engines__POA.PyNode,Generic):
71   """The implementation of the PyNode CORBA IDL"""
72   def __init__(self, nodeName,code,poa,my_container):
73     """Initialize the node : compilation in the local context"""
74     Generic.__init__(self,poa)
75     self.nodeName=nodeName
76     self.code=code
77     self.my_container=my_container._container
78     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
79     ccode=compile(code,nodeName,'exec')
80     self.context={}
81     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
82     exec(ccode, self.context)
83
84   def getContainer(self):
85     return self.my_container
86
87   def getCode(self):
88     return self.code
89
90   def getName(self):
91     return self.nodeName
92
93   def defineNewCustomVar(self,varName,valueOfVar):
94     self.context[varName] = pickle.loads(valueOfVar)
95     pass
96
97   def executeAnotherPieceOfCode(self,code):
98     """Called for initialization of container lodging self."""
99     try:
100       ccode=compile(code,self.nodeName,'exec')
101       exec(ccode, self.context)
102     except Exception:
103       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
104
105   def execute(self,funcName,argsin):
106     """Execute the function funcName found in local context with pickled args (argsin)"""
107     try:
108       argsin,kws=pickle.loads(argsin)
109       func=self.context[funcName]
110       argsout=func(*argsin,**kws)
111       argsout=pickle.dumps(argsout,-1)
112       return argsout
113     except Exception:
114       exc_typ,exc_val,exc_fr=sys.exc_info()
115       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
116       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyNode: %s, function: %s" % (self.nodeName,funcName),0))
117
118 class SenderByte_i(SALOME__POA.SenderByte,Generic):
119   def __init__(self,poa,bytesToSend):
120     Generic.__init__(self,poa)
121     self.bytesToSend = bytesToSend
122
123   def getSize(self):
124     return len(self.bytesToSend)
125
126   def sendPart(self,n1,n2):
127     return self.bytesToSend[n1:n2]
128
129 DicoForProxyFile = { }
130
131 def GetSizeOfBufferedReader(f):
132   """
133   This method returns in bytes size of a file openned.
134
135   Args:
136   ----
137       f (io.IOBase): buffered reader returned by open
138       
139   Returns
140   -------
141       int: number of bytes
142   """
143   import io
144   pos = f.tell()
145   f.seek(0,io.SEEK_END)
146   pos2 = f.tell()
147   f.seek(pos,io.SEEK_SET)
148   return pos2-pos
149
150 def GetObjectFromFile(fname, visitor = None):
151   with open(fname,"rb") as f:
152     if visitor:
153       visitor.setHDDMem( GetSizeOfBufferedReader(f) )
154       visitor.setFileName( fname )
155     obj = pickle.load(f)
156   return obj
157
158 def DumpInFile(obj,fname):
159   with open(fname,"wb") as f:
160     f.write( obj )
161
162 def IncrRefInFile(fname):
163   if fname in DicoForProxyFile:
164     DicoForProxyFile[fname] += 1
165   else:
166     DicoForProxyFile[fname] = 2
167   pass
168
169 def DecrRefInFile(fname):
170   if fname not in DicoForProxyFile:
171     cnt = 1
172   else:
173     cnt = DicoForProxyFile[fname]
174     DicoForProxyFile[fname] -= 1
175     if cnt == 1:
176       del DicoForProxyFile[fname]
177   if cnt == 1:
178     if os.path.exists(fname):
179       os.unlink( fname )
180   pass
181
182 def GetBigObjectOnDiskThreshold():
183     return KernelBasis.GetBigObjOnDiskThreshold()
184
185 def ActivateProxyMecanismOrNot( sizeInByte ):
186   thres = GetBigObjectOnDiskThreshold()
187   if thres == -1:
188     return False
189   else:
190     return sizeInByte > thres
191
192 def GetBigObjectDirectory():
193   import os
194   if not KernelBasis.BigObjOnDiskDirectoryDefined():
195     raise RuntimeError("An object of size higher than limit detected and no directory specified to dump it in file !")
196   return os.path.expanduser( os.path.expandvars( KernelBasis.GetBigObjOnDiskDirectory() ) )
197
198 def GetBigObjectFileName():
199   """
200   Return a filename in the most secure manner (see tempfile documentation)
201   """
202   import tempfile
203   with tempfile.NamedTemporaryFile(dir=GetBigObjectDirectory(),prefix="mem_",suffix=".pckl") as f:
204     ret = f.name
205   return ret
206
207 class BigObjectOnDiskBase:
208   def __init__(self, fileName, objSerialized):
209     """
210     :param fileName: the file used to dump into.
211     :param objSerialized: the object in pickeled form
212     :type objSerialized: bytes
213     """
214     self._filename = fileName
215     # attribute _destroy is here to tell client side or server side
216     # only client side can be with _destroy set to True. server side due to risk of concurrency
217     # so pickled form of self must be done with this attribute set to False.
218     self._destroy = False
219     self.__dumpIntoFile(objSerialized)
220
221   def getDestroyStatus(self):
222     return self._destroy
223
224   def incrRef(self):
225     if self._destroy:
226       IncrRefInFile( self._filename )
227     else:
228       # should never happen !
229       RuntimeError("Invalid call to incrRef !")
230
231   def decrRef(self):
232     if self._destroy:
233       DecrRefInFile( self._filename )
234     else:
235       # should never happen !
236       RuntimeError("Invalid call to decrRef !")
237
238   def unlinkOnDestructor(self):
239     self._destroy = True
240
241   def doNotTouchFile(self):
242     """
243     Method called slave side. The life cycle management of file is client side not slave side.
244     """
245     self._destroy = False
246
247   def __del__(self):
248     if self._destroy:
249       DecrRefInFile( self._filename )
250
251   def getFileName(self):
252     return self._filename
253   
254   def __dumpIntoFile(self, objSerialized):
255     DumpInFile( objSerialized, self._filename )
256
257   def get(self, visitor = None):
258     obj = GetObjectFromFile( self._filename, visitor )
259     return obj
260
261   def __float__(self):
262     return float( self.get() )
263     
264   def __int__(self):
265     return int( self.get() )
266     
267   def __str__(self):
268     obj = self.get()
269     if isinstance(obj,str):
270         return obj
271     else:
272         raise RuntimeError("Not a string")
273       
274 class BigObjectOnDisk(BigObjectOnDiskBase):
275   def __init__(self, fileName, objSerialized):
276     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
277     
278 class BigObjectOnDiskListElement(BigObjectOnDiskBase):
279   def __init__(self, pos, length, fileName):
280     self._filename = fileName
281     self._destroy = False
282     self._pos = pos
283     self._length = length
284
285   def get(self, visitor = None):
286     fullObj = BigObjectOnDiskBase.get(self, visitor)
287     return fullObj[ self._pos ]
288     
289   def __getitem__(self, i):
290     return self.get()[i]
291
292   def __len__(self):
293     return len(self.get())
294     
295 class BigObjectOnDiskSequence(BigObjectOnDiskBase):
296   def __init__(self, length, fileName, objSerialized):
297     BigObjectOnDiskBase.__init__(self, fileName, objSerialized)
298     self._length = length
299
300   def __getitem__(self, i):
301     return BigObjectOnDiskListElement(i, self._length, self.getFileName())
302
303   def __len__(self):
304     return self._length
305
306 class BigObjectOnDiskList(BigObjectOnDiskSequence):
307   def __init__(self, length, fileName, objSerialized):
308     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
309     
310 class BigObjectOnDiskTuple(BigObjectOnDiskSequence):
311   def __init__(self, length, fileName, objSerialized):
312     BigObjectOnDiskSequence.__init__(self, length, fileName, objSerialized)
313
314 def ProxyfyPickeled( obj, pickleObjInit = None, visitor = None ):
315   """
316   This method return a proxy instance of pickled form of object given in input.
317
318   Args:
319   ----
320       obj (pickelable type) : object to be proxified
321       pickleObjInit (bytes) : Optionnal. Original pickeled form of object to be proxyfied if already computed. If not this method generate it
322
323   Returns
324   -------
325       BigObjectOnDiskBase: proxy instance
326   """
327   pickleObj = pickleObjInit
328   if pickleObj is None:
329     pickleObj = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
330   fileName = GetBigObjectFileName()
331   if visitor:
332     visitor.setHDDMem( len(pickleObj) )
333     visitor.setFileName(fileName)
334   if isinstance( obj, list):
335     proxyObj = BigObjectOnDiskList( len(obj), fileName, pickleObj )
336   elif isinstance( obj, tuple):
337     proxyObj = BigObjectOnDiskTuple( len(obj), fileName , pickleObj )
338   else:
339     proxyObj = BigObjectOnDisk( fileName , pickleObj )
340   return proxyObj
341
342 def SpoolPickleObject( obj, visitor = None ):
343   import pickle
344   with InOutputObjVisitorCM(visitor) as v:
345     pickleObjInit = pickle.dumps( obj , pickle.HIGHEST_PROTOCOL )
346     if not ActivateProxyMecanismOrNot( len(pickleObjInit) ):
347       return pickleObjInit
348     else:
349       proxyObj = ProxyfyPickeled( obj, pickleObjInit, v.visitor() )
350       pickleProxy = pickle.dumps( proxyObj , pickle.HIGHEST_PROTOCOL )
351       return pickleProxy
352
353 from SALOME_ContainerHelper import InOutputObjVisitorCM, InOutputObjVisitor
354
355 def UnProxyObjectSimple( obj, visitor = None ):
356   """
357   Method to be called in Remote mode. Alterate the obj _status attribute. 
358   Because the slave process does not participate in the reference counting
359   
360   Args:
361   ----
362       visitor (InOutputObjVisitor): A visitor to keep track of amount of memory on chip and those on HDD
363
364   """
365   with InOutputObjVisitorCM(visitor) as v:
366     logging.debug( "UnProxyObjectSimple {}".format(type(obj)) )
367     if isinstance(obj,BigObjectOnDiskBase):
368       obj.doNotTouchFile()
369       return obj.get( v )
370     elif isinstance( obj, list):
371       retObj = []
372       for elt in obj:
373         retObj.append( UnProxyObjectSimple(elt,v.visitor()) )
374       return retObj
375     else:
376       return obj
377
378 def UnProxyObjectSimpleLocal( obj ):
379   """
380   Method to be called in Local mode. Do not alterate the PyObj counter
381   """
382   if isinstance(obj,BigObjectOnDiskBase):
383     return obj.get()
384   elif isinstance( obj, list):
385     retObj = []
386     for elt in obj:
387       retObj.append( UnProxyObjectSimpleLocal(elt) )
388     return retObj
389   else:
390     return obj
391   
392 class FileHolder:
393   def __init__(self, fileName):
394     self._filename = fileName
395   @property
396   def filename(self):
397     return self._filename
398   
399 class FileDeleter(FileHolder):
400   def __init__(self, fileName):
401     super().__init__( fileName )
402   def __del__(self):
403     import os
404     if os.path.exists( self._filename ):
405       os.unlink( self._filename )
406
407 class MonitoringInfo:
408   def __init__(self, pyFileName, intervalInMs, outFileName, pid):
409     self._py_file_name = pyFileName
410     self._interval_in_ms = intervalInMs
411     self._out_file_name = outFileName
412     self._pid = pid
413
414   @property
415   def pyFileName(self):
416     return self._py_file_name
417
418   @property
419   def pid(self):
420     return self._pid
421   
422   @pid.setter
423   def pid(self, value):
424     self._pid = value
425
426   @property
427   def outFileName(self):
428     return self._out_file_name
429   
430   @property
431   def intervalInMs(self):
432     return self._interval_in_ms
433   
434 def FileSystemMonitoring(intervalInMs, dirNameToInspect, outFileName = None):
435     """
436     This method loops indefinitely every intervalInMs milliseconds to scan 
437     number of inodes and size of content recursively included into the in input directory.
438
439     Args:
440     ----
441
442     outFileName (str) : name of file inside the results will be written. If None a new file is generated
443
444     See also CPUMemoryMonitoring
445     """
446     global orb
447     import os
448     dirNameToInspect2 = os.path.abspath( os.path.expanduser(dirNameToInspect) )
449     import tempfile
450     import logging
451     import KernelBasis
452     # outFileNameSave stores the content of outFileName during phase of dumping
453     with tempfile.NamedTemporaryFile(prefix="fs_monitor_",suffix=".txt") as f:
454       outFileNameSave = f.name
455     with tempfile.NamedTemporaryFile(prefix="fs_monitor_",suffix=".py") as f:
456       tempPyFile = f.name
457     tempOutFile = outFileName
458     if tempOutFile is None:
459       tempOutFile = "{}.txt".format( os.path.splitext( tempPyFile )[0] )
460     with open(tempPyFile,"w") as f:
461         f.write("""
462 import subprocess as sp
463 import re
464 import os
465 import time
466 import datetime
467 with open("{tempOutFile}","a") as f:
468   f.write( "{{}}\\n".format( "{dirNameToInspect2}" ) )
469   f.write( "{{}}\\n".format( "{intervalInMs}" ) )
470   while(True):
471     nbinodes = -1
472     try:
473       nbinodes = sp.check_output("{{}} | wc -l".format( " ".join(["find","{dirNameToInspect2}"]),  ), shell = True).decode().strip()
474     except:
475       pass
476     szOfDirStr = "fail"
477     try:
478       st = sp.check_output(["du","-sh","{dirNameToInspect2}"]).decode()
479       szOfDirStr = re.split("[\s]+",st)[0]
480     except:
481       pass
482     f.write( "{{}}\\n".format( str( datetime.datetime.now().timestamp() ) ) )
483     f.write( "{{}}\\n".format( str( nbinodes  ) ) )
484     f.write( "{{}}\\n".format( str( szOfDirStr ) ) )
485     f.flush()
486     time.sleep( {intervalInMs} / 1000.0 )
487 """.format( **locals()))
488     logging.debug( "File for FS monitoring dump file : {}".format(tempPyFile) )
489     pyFileName = FileDeleter( tempPyFile )
490     if outFileName is None:
491       outFileName = FileDeleter( tempOutFile )
492     else:
493       outFileName = FileHolder(outFileName)
494     return MonitoringInfo(pyFileName, intervalInMs, outFileName, None)
495
496 def CPUMemoryMonitoring( intervalInMs, outFileName = None ):
497   """
498   Launch a subprocess monitoring self process.
499   This monitoring subprocess is a python process lauching every intervalInMs ms evaluation of
500   CPU usage and RSS memory of the calling process.
501   Communication between subprocess and self is done by file.
502
503   Args:
504   ----
505     outFileName (str) : name of file inside the results will be written. If None a new file is generated
506
507   See also FileSystemMonitoring
508   """
509   import KernelBasis
510   def BuildPythonFileForCPUPercent( intervalInMs, outFileName):
511     import os
512     import tempfile
513     with tempfile.NamedTemporaryFile(prefix="cpu_mem_monitor_",suffix=".py") as f:
514       tempPyFile = f.name
515     tempOutFile = outFileName
516     if tempOutFile is None:
517       tempOutFile = "{}.txt".format( os.path.splitext( tempPyFile )[0] )
518     pid = os.getpid()
519     with open(tempPyFile,"w") as f:
520       f.write("""import psutil
521 pid = {}
522 process = psutil.Process( pid )
523 def getChargeOf( p ):
524   a,b = p.cpu_percent(), p.memory_info().rss
525   try:
526     for c in p.children():
527       a += c.cpu_percent(interval=0.01) ; b += c.memory_info().rss
528   except:
529     pass
530   return a,b
531 import time
532 with open("{}","a") as f:
533   f.write( "{{}}\\n".format( "{}" ) )
534   while True:
535     cpu,mem_rss = getChargeOf( process )
536     f.write( "{{}}\\n".format( str( cpu ) ) )
537     f.write( "{{}}\\n".format( str( mem_rss  ) ) )
538     f.flush()
539     time.sleep( {} / 1000.0 )
540 """.format(pid, tempOutFile, intervalInMs, intervalInMs))
541     if outFileName is None:
542       autoOutFile = FileDeleter(tempOutFile)
543     else:
544       autoOutFile = FileHolder(tempOutFile)
545     return FileDeleter(tempPyFile),autoOutFile
546   pyFileName, outFileName = BuildPythonFileForCPUPercent( intervalInMs, outFileName )
547   return MonitoringInfo(pyFileName, intervalInMs, outFileName, None)
548
549 class GenericPythonMonitoringLauncherCtxMgr:
550     def __init__(self, monitoringParams):
551         """
552         Args:
553         ----
554             monitoringParams (MonitoringInfo)
555         """
556         self._monitoring_params = monitoringParams
557     def __enter__(self):
558         import KernelBasis
559         pid = KernelBasis.LaunchMonitoring(self._monitoring_params.pyFileName.filename)
560         self._monitoring_params.pid = pid
561         return self._monitoring_params
562     
563     def __exit__(self,exctype, exc, tb):
564         StopMonitoring( self._monitoring_params )
565         del self._monitoring_params
566         import gc
567         gc.collect() # force destruction of objects even in raise context
568
569 def StopMonitoring( monitoringInfo ):
570   """
571   Kill monitoring subprocess.
572
573   Args:
574   ----
575       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
576   """
577   import KernelBasis
578   KernelBasis.StopMonitoring(monitoringInfo.pid)
579
580 class CPUMemInfo:
581   def __init__(self, intervalInMs, cpu, mem_rss):
582     """
583     Args:
584     ----
585     intervalInMs (int)
586     cpu (list<float>)  CPU usage
587     mem_rss (list<int>) rss memory usage
588     """
589     self._interval_in_ms = intervalInMs
590     self._data = [(a,b) for a,b in zip(cpu,mem_rss)]
591   def __str__(self):
592     st = """Interval in ms : {self.intervalInMs}
593 Data : ${self.data}
594 """.format( **locals() )
595     return st
596   @property
597   def intervalInMs(self):
598     return self._interval_in_ms
599   @property
600   def data(self):
601     """
602     list of triplets. First param of pair is cpu usage 
603                       Second param of pair is memory usage
604     """
605     return self._data
606
607 def ReadCPUMemInfoInternal( fileName ):
608   intervalInMs = 0
609   cpu = [] ; mem_rss = []
610   if os.path.exists( fileName ):
611     try:
612       with open(fileName, "r") as f:
613         coarseData = [ elt.strip() for elt in f.readlines() ]
614       intervalInMs = int( coarseData[0] )
615       coarseData = coarseData[1:]
616       cpu = [float(elt) for elt in coarseData[::2]]
617       mem_rss = [ int(elt) for elt in coarseData[1::2]]
618     except:
619       pass
620   return CPUMemInfo(intervalInMs,cpu,mem_rss)
621
622 def ReadCPUMemInfo( monitoringInfo ):
623   """
624   Retrieve CPU/Mem data of monitoring.
625
626   Args:
627   ----
628       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
629   
630   Returns
631   -------
632     CPUMemInfo instance
633   """
634   return ReadCPUMemInfoInternal( monitoringInfo.outFileName.filename )
635
636 class InodeSizeInfo:
637   def __init__(self, dirNameMonitored, intervalInMs, timeStamps, nbInodes, volumeOfDir):
638     """
639     Args:
640     ----
641     timeStamps (list<datetimestruct>)
642     nbInodes (list<int>)
643     volumeOfDir (list<str>)
644     """
645     self._dir_name_monitored = dirNameMonitored
646     self._interval_in_ms = intervalInMs
647     self._data = [(t,a,b) for t,a,b in zip(timeStamps,nbInodes,volumeOfDir)]
648   def __str__(self):
649     st = """Filename monitored : {self.dirNameMonitored}
650 Interval in ms : ${self.intervalInMs}
651 Data : ${self.data}
652 """.format( **locals() )
653     return st
654   @property
655   def dirNameMonitored(self):
656     return self._dir_name_monitored
657   @property
658   def intervalInMs(self):
659     return self._interval_in_ms
660   @property
661   def data(self):
662     """
663     list of triplets. First param of triplet is datetimestruct
664                                       Second param of triplet is #inodes.
665                                       Thirst param of triplet is size.
666     """
667     return self._data
668
669 def ReadInodeSizeInfoInternal( fileName ):
670   import datetime
671   import os
672   with open(fileName, "r") as f:
673     coarseData = [ elt.strip() for elt in f.readlines() ]
674   dirNameMonitored = coarseData[0] ; intervalInMs = int( coarseData[1] ) ; coarseData = coarseData[2:]
675   tss = [ datetime.datetime.fromtimestamp( float(elt) ) for elt in coarseData[::3] ]
676   nbInodes = [int(elt) for elt in coarseData[1::3]]
677   volumeOfDir = coarseData[2::3]
678   return InodeSizeInfo(dirNameMonitored,intervalInMs,tss,nbInodes,volumeOfDir)
679
680 def ReadInodeSizeInfo( monitoringInfo ):
681   """
682   Retrieve nb of inodes and size of monitoring
683
684   Args:
685   ----
686       monitoringInfo (MonitoringInfo): info returned by LaunchMonitoring
687
688   Returns
689   -------
690     InodeSizeInfo
691   """
692   return ReadInodeSizeInfoInternal( monitoringInfo.outFileName.filename )
693
694 class SeqByteReceiver:
695   # 2GB limit to trigger split into chunks
696   CHUNK_SIZE = 2000000000
697   def __init__(self,sender):
698     self._obj = sender
699   def __del__(self):
700     self._obj.UnRegister()
701     pass
702   def data(self):
703     size = self._obj.getSize()
704     if size <= SeqByteReceiver.CHUNK_SIZE:
705       return self.fetchOneShot( size )
706     else:
707       return self.fetchByChunks( size )
708   def fetchOneShot(self,size):
709     return self._obj.sendPart(0,size)
710   def fetchByChunks(self,size):
711       """
712       To avoid memory peak parts over 2GB are sent using EFF_CHUNK_SIZE size.
713       """
714       data_for_split_case = bytes(0)
715       EFF_CHUNK_SIZE = SeqByteReceiver.CHUNK_SIZE // 8
716       iStart = 0 ; iEnd = EFF_CHUNK_SIZE
717       while iStart!=iEnd and iEnd <= size:
718         part = self._obj.sendPart(iStart,iEnd)
719         data_for_split_case = bytes(0).join( [data_for_split_case,part] )
720         iStart = iEnd; iEnd = min(iStart + EFF_CHUNK_SIZE,size)
721       return data_for_split_case
722   
723 FinalCode = """import pickle
724 from SALOME_PyNode import LogOfCurrentExecutionSession,MY_PERFORMANCE_LOG_ENTRY_IN_GLBS
725 import CORBA
726 import Engines
727 orb = CORBA.ORB_init([''])
728 codeFileName = "{}"
729 inputFileName = "{}"
730 outputFileName = "{}"
731 outputsKeys = {}
732 exec( "{{}} = LogOfCurrentExecutionSession( orb.string_to_object( \\"{}\\" ) )".format(MY_PERFORMANCE_LOG_ENTRY_IN_GLBS) )
733 with open(inputFileName,"rb") as f:
734   context = pickle.load( f )
735 context[MY_PERFORMANCE_LOG_ENTRY_IN_GLBS] = eval( MY_PERFORMANCE_LOG_ENTRY_IN_GLBS )
736 with open(codeFileName,"r") as f:
737   code = f.read()
738 #
739 import gc
740 gc.disable()
741 # go for execution
742 exec( code , context )
743 # filter part of context to be exported to father process
744 context = dict( [(k,v) for k,v in context.items() if k in outputsKeys] )
745 #
746 with open(outputFileName,"wb") as f:
747   pickle.dump( context, f )
748 """
749
750 class PythonFunctionEvaluatorParams:
751   def __init__(self, mainFileName, codeFileName, inContextFileName, outContextFileName):
752     self._main_filename = mainFileName
753     self._code_filename = codeFileName
754     self._in_context_filename = inContextFileName
755     self._out_context_filename = outContextFileName
756   @property
757   def result(self):
758     import pickle
759     with open(self._out_context_filename,"rb") as f:
760       return pickle.load( f )
761   def destroyOnOK(self):
762     for fileToDestroy in [self._main_filename,self._code_filename,self._in_context_filename,self._out_context_filename]:
763       if os.path.exists( fileToDestroy ):
764         os.unlink( fileToDestroy )
765   def destroyOnKO(self, containerRef):
766      """
767      Called in the context of failure with replay mode activated
768      """
769      for fileToDestroy in [self._out_context_filename]:
770       if os.path.exists( fileToDestroy ):
771         os.unlink( fileToDestroy )
772       # register to container files group associated to the
773       containerRef.addLogFileNameGroup([self._main_filename,self._code_filename,self._in_context_filename])
774   @property
775   def replayCmd(self):
776     return "To replay : ( cd {} && python3 {} )".format(os.path.dirname(self._main_filename),os.path.basename(self._main_filename))
777   
778   @property
779   def cleanOperations(self):
780     import os
781     return "To clean files : ( cd {} && rm {} )".format( os.path.dirname(self._main_filename)," ".join( [os.path.basename(self._main_filename),self._code_filename,self._in_context_filename] ) )
782
783   def strDependingOnReturnCode(self, keepFilesToReplay, returnCode):
784     if returnCode == -1:
785       return f"return with non zero code ({returnCode})"
786     else:
787       banner = 200*"*"
788       if keepFilesToReplay:
789         return f"""return with non zero code ({returnCode})
790 {banner}
791 Looks like a hard crash as returnCode {returnCode} != 0
792 {self.replayCmd}
793 {self.cleanOperations}
794 {banner}
795 """
796       else:
797         return f"""return with non zero code ({returnCode})
798 {banner}
799 Looks like a hard crash as returnCode {returnCode} != 0
800 {banner}
801 """
802
803 def ExecCrashProofGeneric( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession, keepFilesToReplay, closeEyesOnErrorAtExit):
804   """
805   Equivalent of exec(code,context) but executed in a separate subprocess to avoid to make the current process crash.
806   
807   Args:
808   -----
809
810   code (str) : python code to be executed using context
811   context (dict) : context to be used for execution. This context will be updated in accordance with the execution of code.
812   outargsname (list<str>) : list of arguments to be exported 
813   containerRef (Engines.Container) : Container ref (retrieving the Files to created when keepFilesToReplay is set to False)
814   instanceOfLogOfCurrentSession (LogOfCurrentExecutionSession) : instance of LogOfCurrentExecutionSession to build remotely the reference in order to log information
815   keepFilesToReplay (bool) : if True when something goes wrong during execution all the files to replay post mortem case are kept. If False only error is reported but files to replay are destoyed.
816   closeEyesOnErrorAtExit (bool) : if True in case of crash of subprocess, if MY_KEY_TO_DETECT_FINISH is displayed at the end of stdout
817
818   Return:
819   -------
820
821   ScriptExecInfo : instance serverside
822
823   In/Out:
824   -------
825
826   context will be modified by this method. elts in outargsname will be added and their corresponding value coming from evaluation.
827   """
828   import tempfile
829   import pickle
830   import subprocess as sp
831   import CORBA
832   #
833   def IsConsideredAsOKRun( returnCode, closeEyesOnErrorAtExit , stderr ):
834     def StdErrTreatment(closeEyesOnErrorAtExit , stderr):
835       if not closeEyesOnErrorAtExit:
836         return stderr
837       else:
838         return stderr[:-len(MY_KEY_TO_DETECT_FINISH)]
839     if returnCode == 0:
840       return True,StdErrTreatment(closeEyesOnErrorAtExit , stderr)
841     if not closeEyesOnErrorAtExit:
842       return False, stderr
843     return stderr[-len(MY_KEY_TO_DETECT_FINISH):] == MY_KEY_TO_DETECT_FINISH,stderr[:-len(MY_KEY_TO_DETECT_FINISH)]
844
845   #
846   def InternalExecResistant( code, context, outargsname):
847     import KernelBasis
848     orb = CORBA.ORB_init([''])
849     iorScriptLog = orb.object_to_string( instanceOfLogOfCurrentSession._remote_handle )#ref ContainerScriptPerfLog_ptr
850     ####
851     EXEC_CODE_FNAME_PXF = "execsafe_"
852     def RetrieveUniquePartFromPfx( fname ):
853       return os.path.splitext( os.path.basename(fname)[len(EXEC_CODE_FNAME_PXF):] )[0]
854     with tempfile.NamedTemporaryFile(dir=os.getcwd(),prefix=EXEC_CODE_FNAME_PXF,suffix=".py", mode="w", delete = False) as codeFd:
855       codeFd.write( code )
856       if closeEyesOnErrorAtExit:
857         codeFd.write( """
858 import sys
859 sys.stderr.write({!r})
860 sys.stderr.flush()""".format( MY_KEY_TO_DETECT_FINISH ) )
861       codeFd.flush()
862       codeFileName = os.path.basename( codeFd.name )
863       contextFileName = "contextsafe_{}.pckl".format( RetrieveUniquePartFromPfx( codeFileName  ) )
864       with open(contextFileName,"wb") as contextFd:
865         pickle.dump( context, contextFd)
866       resFileName = "outcontextsafe_{}.pckl".format( RetrieveUniquePartFromPfx( codeFileName  ) )
867       mainExecFileName = os.path.abspath( "mainexecsafe_{}.py".format( RetrieveUniquePartFromPfx( codeFileName  ) ) )
868       with open(mainExecFileName,"w") as f:
869         f.write( FinalCode.format( codeFileName, contextFileName, resFileName, outargsname, iorScriptLog ) )
870       for iTry in range( KernelBasis.GetNumberOfRetry() ):
871         if iTry > 0:
872           print( "WARNING : Retry # {}. Following code has generated non zero return code ( {} ). Trying again ... \n{}".format( iTry, returnCode, code ) )
873         p = sp.Popen(["python3", mainExecFileName],stdout = sp.PIPE, stderr = sp.PIPE)
874         stdout, stderr = p.communicate()
875         returnCode = p.returncode
876         if returnCode == 0:
877           break
878     return returnCode, stdout, stderr, PythonFunctionEvaluatorParams(mainExecFileName,codeFileName,contextFileName,resFileName)
879   ret = instanceOfLogOfCurrentSession._current_instance
880   returnCode, stdout, stderr, evParams = InternalExecResistant( code, context, outargsname )
881   stdout = stdout.decode()
882   stderr = stderr.decode()
883   sys.stdout.write( stdout ) ; sys.stdout.flush()
884   isOK, stderr = IsConsideredAsOKRun( returnCode, closeEyesOnErrorAtExit , stderr )
885   sys.stderr.write( stderr ) ; sys.stderr.flush()
886   if isOK:
887     pcklData = instanceOfLogOfCurrentSession._remote_handle.getObj()
888     if len(pcklData) > 0:
889       ret = pickle.loads( pcklData )
890     context.update( evParams.result )
891     evParams.destroyOnOK()
892     if returnCode != 0:
893       print( "WARNING : Following code has generated non zero return code ( {} ) but considered as OK\n{}".format( returnCode, code ) )
894     return ret
895   else:
896     if keepFilesToReplay:
897       evParams.destroyOnKO( containerRef )
898     else:
899       evParams.destroyOnOK()
900     raise RuntimeError(f"Subprocess launched {evParams.strDependingOnReturnCode(keepFilesToReplay,returnCode)}stdout :\n{stdout}\nstderr :\n{stderr}")
901
902 def ExecCrashProofWithReplay( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession ):
903   return ExecCrashProofGeneric(code, context, outargsname, containerRef, instanceOfLogOfCurrentSession, True, False)
904
905 def ExecCrashProofWithoutReplay( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession ):
906   return ExecCrashProofGeneric(code, context, outargsname, containerRef, instanceOfLogOfCurrentSession, False, False)
907
908 def ExecCrashProofWithReplayFT( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession ):
909   return ExecCrashProofGeneric(code, context, outargsname, containerRef, instanceOfLogOfCurrentSession, True, True)
910
911 def ExecCrashProofWithoutReplayFT( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession ):
912   return ExecCrashProofGeneric(code, context, outargsname, containerRef, instanceOfLogOfCurrentSession, False, True)
913
914 def ExecLocal( code, context, outargsname, containerRef, instanceOfLogOfCurrentSession ):
915   exec( code, context )
916   return instanceOfLogOfCurrentSession._current_instance
917
918 class LogOfCurrentExecutionSessionAbs(abc.ABC):
919   def __init__(self):
920     self._current_instance = ScriptExecInfo()
921
922   def addInfoOnLevel2(self, key, value):
923     setattr(self._current_instance,key,value)
924
925   @abc.abstractmethod
926   def addFreestyleAndFlush(self, value):
927     raise RuntimeError("Must be overloaded")
928
929 class LogOfCurrentExecutionSession(LogOfCurrentExecutionSessionAbs):
930   def __init__(self, handleToCentralizedInst):
931     super().__init__()
932     self._remote_handle = handleToCentralizedInst
933
934   def addFreestyleAndFlush(self, value):
935     self._current_instance.freestyle = value
936     self.finalizeAndPushToMaster()
937
938   def finalizeAndPushToMaster(self):
939     """
940     Voluntary do nothing in case of problem to avoid to trouble execution
941     """
942     try:
943       self._remote_handle.assign( pickle.dumps( self._current_instance ) )
944     except:
945       pass
946
947 class LogOfCurrentExecutionSessionStub(LogOfCurrentExecutionSessionAbs):
948   """
949   This class is to stub LogOfCurrentExecutionSession in context of replay where the server (handleToCentralizedInst) has vanished
950   """
951   def __init__(self, handleToCentralizedInst = None):
952     super().__init__()
953   def addFreestyleAndFlush(self, value):
954     pass
955
956 class PyScriptNode_Abstract_i(Engines__POA.PyScriptNode,Generic,abc.ABC):
957   """The implementation of the PyScriptNode CORBA IDL that executes a script"""
958   def __init__(self, nodeName, code, poa, my_container, logscript):
959     """Initialize the node : compilation in the local context"""
960     Generic.__init__(self,poa)
961     self.nodeName=nodeName
962     self.code=code
963     self.my_container_py = my_container
964     self.my_container=my_container._container
965     linecache.cache[nodeName]=0,None,code.split('\n'),nodeName
966     self.ccode=compile(code,nodeName,'exec')
967     self.context={}
968     self.context[MY_CONTAINER_ENTRY_IN_GLBS] = self.my_container
969     self._log_script = logscript
970     self._current_execution_session = None
971     sys.stdout.flush() ; sys.stderr.flush() # flush to correctly capture log per execution session
972
973   @abc.abstractmethod
974   def executeNow(self, outargsname):
975     raise RuntimeError("Must be overloaded")
976       
977   def __del__(self):
978     # force removal of self.context. Don t know why it s not done by default
979     self.removeAllVarsInContext()
980     pass
981
982   def getContainer(self):
983     return self.my_container
984
985   def getCode(self):
986     return self.code
987
988   def getName(self):
989     return self.nodeName
990
991   def defineNewCustomVar(self,varName,valueOfVar):
992     self.context[varName] = pickle.loads(valueOfVar)
993     pass
994
995   def executeAnotherPieceOfCode(self,code):
996     """Called for initialization of container lodging self."""
997     try:
998       ccode=compile(code,self.nodeName,'exec')
999       exec(ccode, self.context)
1000     except Exception:
1001       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode (%s) : code to be executed \"%s\"" %(self.nodeName,code),0))
1002
1003   def assignNewCompiledCode(self,codeStr):
1004     try:
1005       self.code=codeStr
1006       self.ccode=compile(codeStr,self.nodeName,'exec')
1007     except Exception:
1008       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"","PyScriptNode.assignNewCompiledCode (%s) : code to be executed \"%s\"" %(self.nodeName,codeStr),0))
1009
1010   def executeSimple(self, key, val):
1011     """
1012     Same as execute method except that no pickelization mecanism is implied here. No output is expected
1013     """
1014     try:
1015       self.context.update({ "env" : [(k,v) for k,v in zip(key,val)]})
1016       exec(self.ccode,self.context)
1017     except Exception:
1018       exc_typ,exc_val,exc_fr=sys.exc_info()
1019       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1020       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
1021       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" % (self.nodeName),0))
1022     
1023   def execute(self,outargsname,argsin):
1024     """Execute the script stored in attribute ccode with pickled args (argsin)"""
1025     try:
1026       argsname,kws=pickle.loads(argsin)
1027       self.context.update(kws)
1028       exec(self.ccode, self.context)
1029       argsout=[]
1030       for arg in outargsname:
1031         if arg not in self.context:
1032           raise KeyError("There is no variable %s in context" % arg)
1033         argsout.append(self.context[arg])
1034       argsout=pickle.dumps(tuple(argsout),-1)
1035       return argsout
1036     except Exception:
1037       exc_typ,exc_val,exc_fr=sys.exc_info()
1038       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1039       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
1040       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s, outargsname: %s" % (self.nodeName,outargsname),0))
1041
1042   def executeFirst(self,argsin):
1043     """ Same than first part of self.execute to reduce memory peak."""
1044     def ArgInMananger(self,argsin):
1045       argsInPy = SeqByteReceiver( argsin )
1046       data = argsInPy.data()
1047       self.addInfoOnLevel2("inputMem",len(data))
1048       _,kws=pickle.loads(data)
1049       return kws
1050     try:
1051       self.beginOfCurrentExecutionSession()
1052       self.addTimeInfoOnLevel2("startInputTime")
1053       # to force call of SeqByteReceiver's destructor
1054       kws = ArgInMananger(self,argsin)
1055       vis = InOutputObjVisitor()
1056       for elt in kws:
1057         # fetch real data if necessary
1058         kws[elt] = UnProxyObjectSimple( kws[elt],vis)
1059       self.addInfoOnLevel2("inputHDDMem",vis)
1060       self.context.update(kws)
1061       self.addTimeInfoOnLevel2("endInputTime")
1062     except Exception:
1063       exc_typ,exc_val,exc_fr=sys.exc_info()
1064       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1065       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
1066       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:First %s" % (self.nodeName),0))
1067
1068   def executeSecond(self,outargsname):
1069     """ Same than second part of self.execute to reduce memory peak."""
1070     def executeSecondInternal(monitoringtimeresms):
1071       with GenericPythonMonitoringLauncherCtxMgr( CPUMemoryMonitoring( monitoringtimeresms ) ) as monitoringParams:
1072         currentInstance = self.executeNow( outargsname )
1073         cpumeminfo = ReadCPUMemInfo( monitoringParams )
1074       return cpumeminfo, currentInstance
1075
1076     import sys
1077     try:
1078       self.addTimeInfoOnLevel2("startExecTime")
1079       ##
1080       self.addInfoOnLevel2("measureTimeResolution",self.my_container_py.monitoringtimeresms())
1081       cpumeminfo, self._current_execution_session._current_instance = executeSecondInternal( self.my_container_py.monitoringtimeresms() )
1082       ##
1083       self.addInfoOnLevel2("CPUMemDuringExec",cpumeminfo)
1084       self.addTimeInfoOnLevel2("endExecTime")
1085       self.addTimeInfoOnLevel2("startOutputTime")
1086       argsout=[]
1087       for arg in outargsname:
1088         if arg not in self.context:
1089           raise KeyError("There is no variable %s in context" % arg)
1090         argsout.append(self.context[arg])
1091       ret = [ ]
1092       outputMem = 0
1093       vis = InOutputObjVisitor()
1094       for arg in argsout:
1095         # the proxy mecanism is catched here
1096         argPickle = SpoolPickleObject( arg, vis )
1097         retArg = SenderByte_i( self.poa,argPickle )
1098         id_o = self.poa.activate_object(retArg)
1099         retObj = self.poa.id_to_reference(id_o)
1100         ret.append( retObj._narrow( SALOME.SenderByte ) )
1101         outputMem += len(argPickle)
1102       self.addInfoOnLevel2("outputMem",outputMem)
1103       self.addInfoOnLevel2("outputHDDMem",vis)
1104       self.addTimeInfoOnLevel2("endOutputTime")
1105       self.endOfCurrentExecutionSession()
1106       return ret
1107     except Exception:
1108       exc_typ,exc_val,exc_fr=sys.exc_info()
1109       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1110       print("".join(l)) ; sys.stdout.flush() # print error also in logs of remote container
1111       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode:Second %s, outargsname: %s" % (self.nodeName,outargsname),0))
1112
1113   def listAllVarsInContext(self):
1114       import re
1115       pat = re.compile("^__([a-z]+)__$")
1116       return [elt for elt in self.context if not pat.match(elt) and elt != MY_CONTAINER_ENTRY_IN_GLBS]
1117       
1118   def removeAllVarsInContext(self):
1119       for elt in self.listAllVarsInContext():
1120         del self.context[elt]
1121
1122   def getValueOfVarInContext(self,varName):
1123     try:
1124       return pickle.dumps(self.context[varName],-1)
1125     except Exception:
1126       exc_typ,exc_val,exc_fr=sys.exc_info()
1127       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1128       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
1129     pass
1130   
1131   def assignVarInContext(self, varName, value):
1132     try:
1133       self.context[varName][0] = pickle.loads(value)
1134     except Exception:
1135       exc_typ,exc_val,exc_fr=sys.exc_info()
1136       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1137       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
1138     pass
1139
1140   def callMethodOnVarInContext(self, varName, methodName, args):
1141     try:
1142       return pickle.dumps( getattr(self.context[varName][0],methodName)(*pickle.loads(args)),-1 )
1143     except Exception:
1144       exc_typ,exc_val,exc_fr=sys.exc_info()
1145       l=traceback.format_exception(exc_typ,exc_val,exc_fr)
1146       raise SALOME.SALOME_Exception(SALOME.ExceptionStruct(SALOME.BAD_PARAM,"".join(l),"PyScriptNode: %s" %self.nodeName,0))
1147     pass
1148
1149   def beginOfCurrentExecutionSession(self):
1150     self._current_execution_session = LogOfCurrentExecutionSession( self._log_script.addExecutionSession() )
1151     self.context[MY_PERFORMANCE_LOG_ENTRY_IN_GLBS] = self._current_execution_session
1152   
1153   def endOfCurrentExecutionSession(self):
1154     self._current_execution_session.finalizeAndPushToMaster()
1155     self._current_execution_session = None
1156
1157   def addInfoOnLevel2(self, key, value):
1158     self._current_execution_session.addInfoOnLevel2(key, value)
1159       
1160   def addTimeInfoOnLevel2(self, key):
1161     from datetime import datetime
1162     self._current_execution_session.addInfoOnLevel2(key,datetime.now())
1163
1164 class PyScriptNode_i(PyScriptNode_Abstract_i):
1165   def __init__(self, nodeName, code, poa, my_container, logscript):
1166     super().__init__(nodeName, code, poa, my_container, logscript)
1167
1168   def executeNow(self, outargsname):
1169     return ExecLocal(self.ccode,self.context,outargsname,self.my_container,self._current_execution_session)
1170     
1171 class PyScriptNode_OutOfProcess_i(PyScriptNode_Abstract_i):
1172   def __init__(self, nodeName, code, poa, my_container, logscript):
1173     super().__init__(nodeName, code, poa, my_container, logscript)
1174
1175   def executeNow(self, outargsname):
1176     return ExecCrashProofWithoutReplay(self.code,self.context,outargsname,self.my_container,self._current_execution_session)
1177
1178 class PyScriptNode_OutOfProcess_Replay_i(PyScriptNode_Abstract_i):
1179   def __init__(self, nodeName, code, poa, my_container, logscript):
1180     super().__init__(nodeName, code, poa, my_container, logscript)
1181
1182   def executeNow(self, outargsname):
1183     return ExecCrashProofWithReplay(self.code,self.context,outargsname,self.my_container,self._current_execution_session)
1184
1185 class PyScriptNode_OutOfProcess_FT_i(PyScriptNode_Abstract_i):
1186   def __init__(self, nodeName, code, poa, my_container, logscript):
1187     super().__init__(nodeName, code, poa, my_container, logscript)
1188
1189   def executeNow(self, outargsname):
1190     return ExecCrashProofWithoutReplayFT(self.code,self.context,outargsname,self.my_container,self._current_execution_session)
1191
1192 class PyScriptNode_OutOfProcess_Replay_FT_i(PyScriptNode_Abstract_i):
1193   def __init__(self, nodeName, code, poa, my_container, logscript):
1194     super().__init__(nodeName, code, poa, my_container, logscript)
1195
1196   def executeNow(self, outargsname):
1197     return ExecCrashProofWithReplayFT(self.code,self.context,outargsname,self.my_container,self._current_execution_session)