Back to index

salome-kernel  6.5.0
threadhelper.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 # Copyright (C) 2007-2012  CEA/DEN, EDF R&D, OPEN CASCADE
00003 #
00004 # Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
00005 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
00006 #
00007 # This library is free software; you can redistribute it and/or
00008 # modify it under the terms of the GNU Lesser General Public
00009 # License as published by the Free Software Foundation; either
00010 # version 2.1 of the License.
00011 #
00012 # This library is distributed in the hope that it will be useful,
00013 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015 # Lesser General Public License for more details.
00016 #
00017 # You should have received a copy of the GNU Lesser General Public
00018 # License along with this library; if not, write to the Free Software
00019 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
00020 #
00021 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
00022 #
00023 
00024 __author__="gboulant"
00025 __date__ ="$1 avr. 2010 18:12:38$"
00026 
00027 import time
00028 import threading
00029 
00030 # ===========================================================================
00031 class Runner(threading.Thread):
00032     """
00033     This class provides a tool to run and drive a function in a dedicated thread.
00034     """
00035     def __init__(self, functionToRun=None,*argv):
00036         threading.Thread.__init__( self )
00037         self._stopevent = threading.Event()
00038         self.setFunction(functionToRun)
00039         self.setArguments(*argv)
00040         self._exception = None
00041         self._return    = None
00042         self._observer  = None
00043         self._callbackFunction = None
00044         # We create an id from the name and the time date in milliseconds
00045         self._id = functionToRun.__name__ +"/"+ str(time.time())
00046 
00047     def setFunction(self,functionToRun):
00048         """
00049         Positionne la fonction � ex�cuter. La fonction peut �tre la
00050         m�thode d'un objet pass�e sous la forme 'monobjet.mamethode'.
00051         """
00052         self.clear()
00053         self._function = functionToRun
00054 
00055     def setArguments(self,*argv):
00056         """
00057         Positionne les arguments � passer � la fonction
00058         """
00059         self.clear()
00060         self._argv = argv
00061 
00062     def getReturn(self):
00063         """
00064         Retourne le resultat de la fonction. En cas d'erreur, on
00065         r�cup�rera l'exception lev�e au moyen de la m�thode
00066         getException().
00067         """
00068         return self._return
00069 
00070     def setCallbackFunction(self, callbackFunction):
00071         self._callbackFunction = callbackFunction
00072 
00073     def setObserver(self, observerToNotify):
00074         """
00075         Permet de sp�cifier un observateur � notifier en fin
00076         d'ex�cution. L'observateur est suppos� �tre un objet qui
00077         impl�mente une m�thode processNotification()
00078         """
00079         try:
00080             observerToNotify.processNotification
00081         except AttributeError:
00082             raise DevelException("The observer should implement the method processNotification()")
00083 
00084         self._observer = observerToNotify
00085 
00086     def run(self):
00087         """
00088         Ex�cution de la fonction. Impl�mentation de la m�thode run
00089         d�clench�e par l'appel � Thread.start().
00090         """
00091         print "##################### threadhelper.run"
00092         if self._function is None: return
00093         try:
00094             self._return = self._function(*self._argv)
00095         except Exception, e:
00096             print e
00097             self._exception = e
00098         self._stopevent.set()
00099         self.notifyObserver()
00100         self.callback()
00101         # _GBO_ Maybe it's no use to have both observers and a callback function.
00102         # One of the two mechanism should be sufficient
00103 
00104     def notifyObserver(self):
00105         if self._observer is None:
00106             # Aucune notification pr�vue
00107             return
00108         try:
00109             self._observer.processNotification()
00110         except AttributeError, att:
00111             if str(att) == "processNotification":
00112                 print "L'observateur n'impl�mente pas la m�thode processNotification()"
00113             else:
00114                 print "La fonction processNotification() a lev� une exception:"
00115                 print att
00116         except Exception, e:
00117             print "La fonction processNotification() a lev� une exception:"
00118             print e
00119 
00120     def callback(self):
00121         if self._callbackFunction is None: return
00122         self._callbackFunction()
00123 
00124     def isEnded(self):
00125         """
00126         Retourne true si la fonction s'est termin�e naturellement
00127         (correctement ou en erreur). Utile pour les client qui
00128         pratique le pooling (interrogation r�p�t�e � intervalle
00129         r�gulier.
00130         """
00131         return self._stopevent.isSet()
00132         # _GBO_ On n'utilise pas isAlive() pour pouvoir ma�triser
00133         # l'indicateur de stop (exemple de la fonction kill)
00134         # return not self.isAlive()
00135 
00136     def getException(self):
00137         return self._exception
00138 
00139     def getId(self):
00140         return self._id
00141 
00142     def wait(self,timeout=None):
00143         """
00144         Met fin au thread apr�s timeout seconde s'il est encore en
00145         ex�cution.
00146         Si le compte-�-rebours est atteind sans que la fonction
00147         functionToRun soit termin�e, alors le runner est laiss� dans
00148         l'�tat 'not Ended', c'est-�-dire que isEnded retourne
00149         false. On peut ainsi distinguer l'arr�t normal de l'arr�t �
00150         l'issue du timeout.
00151         """
00152         threading.Thread.join(self, timeout)
00153 
00154 
00155     def kill(self,message="Arr�t demand�"):
00156         """
00157         Cette m�thode doit �tre appeler pour interrombre le
00158         thread. Cet appel d�clare le thread comme en erreur (exception
00159         mise � disposition par la m�thode getException().
00160         """
00161         # On cr�e un exception indiquant la demande d'interruption
00162         self._exception = Exception(message)
00163         self._stopevent.set()
00164         # _GBO_ ATTENTION
00165         # Un thread python ne peut pas en r�alit� �tre int�rrompu.
00166         # La fonction reste donc en ex�cution m�me si on a rejoint le
00167         # thread appelant.
00168 
00169     def clear(self):
00170         self._stopevent.clear()
00171         self._exception = None
00172         self._return    = None
00173 
00174 # ===========================================================================
00175 CONTINUE=1
00176 STOP=0
00177 
00178 class PeriodicTimer( threading.Thread ):
00179     """
00180     Cette classe permet d'amorcer un compte-�-rebours p�riodique pour
00181     op�rer un m�canisme de pooling. On d�finit la fonction appell�e
00182     p�riodiquement et la fonction terminale.
00183     """
00184     def __init__( self,  loopdelay, initdelay=0,
00185                   periodic_action=None,
00186                   ended_action=None,
00187                   *ended_argv ):
00188 
00189         if periodic_action is None:
00190             self._periodic_action = self.defaultPeriodicAction
00191         else:
00192             self._periodic_action = periodic_action
00193         self._ended_action = ended_action
00194         self._loopdelay = loopdelay
00195         self._initdelay = initdelay
00196         self._running = CONTINUE
00197         self._ended_argv    = ended_argv
00198         threading.Thread.__init__( self )
00199 
00200     def defaultPeriodicAction():
00201         """
00202         Les fonctions 'periodicAction' retournent CONTINU ou STOP
00203         apr�s avoir ex�cut� l'action de fin de boucle. Si STOP est
00204         retourn�, le cycle est interrompu. 
00205         """
00206         return CONTINU
00207 
00208     def run( self ):
00209         if self._initdelay:
00210             time.sleep( self._initdelay )
00211         self._runtime = time.time()
00212         while self._running == CONTINUE:
00213             start = time.time()
00214             self._running  = self._periodic_action()
00215             self._runtime += self._loopdelay
00216             time.sleep( max( 0, self._runtime - start ) )
00217         if self._ended_action is not None:
00218             self._ended_action( *self._ended_argv )
00219 
00220     def stop( self ):
00221         self._running = STOP
00222 
00223 
00224 
00225 #
00226 # ======================================================
00227 # Fonctions de test unitaire
00228 # ======================================================
00229 #
00230 
00231 # ======================================================
00232 import os
00233 testfilename="/tmp/threadhelperTestFile"
00234 def testIfContinue():
00235     print "On examine la pr�sence du fichier ", testfilename
00236     if os.path.exists(testfilename):
00237         return STOP
00238     else:
00239         return CONTINUE
00240 
00241 def endedAction():
00242     print "FINI"
00243 
00244 def TEST_PeriodicTimer():
00245     periodicTimer=PeriodicTimer(1,0,testIfContinue, endedAction)
00246     periodicTimer.start()
00247 
00248 
00249 # ======================================================
00250 def function_ok(nbsteps=5):
00251     """
00252     Fonction qui se termine correctement
00253     """
00254     print "D�but"
00255     cnt=0
00256     while ( cnt < nbsteps ):
00257         print "Etape ", cnt
00258         time.sleep(0.6)
00259         cnt+=1
00260 
00261     print "Fin"
00262 
00263 def function_with_exception():
00264     """
00265     Fonction qui aboutie � une lev�e d'exception
00266     """
00267     print "D�but"
00268     cnt=0
00269     while ( cnt < 5 ):
00270         print "Etape ", cnt
00271         time.sleep(1)
00272         cnt+=1
00273     
00274     raise Exception("erreur d'ex�cution de la fonction")
00275     print "Fin"
00276 
00277 def infinite_function():
00278     """
00279     fonction de dur�e infinie (tant qu'il y a du courant �l�ctrique) pour
00280     le test du timeout.
00281     """
00282     print "D�but"
00283     cnt=0
00284     while ( 1 ):
00285         print "Etape ", cnt
00286         time.sleep(1)
00287         cnt+=1
00288     
00289     raise Exception("erreur")
00290     print "Fin"
00291 
00292 
00293 def runWithRunner(functionToRun):
00294     """
00295     Ex�cute la fonction avec le runner. On illustre ici la modalit�
00296     d'utilisation du Runner.
00297     """
00298     print "###########"
00299     runner = Runner(functionToRun)
00300     runner.start()
00301 
00302     while ( not runner.isEnded() ):
00303         print "La fonction est en cours"
00304         time.sleep(0.2)
00305     e = runner.getException()
00306     if e is not None:
00307         print "La fonction s'est termin�e en erreur"
00308         print e
00309         # On peut en fait la relancer
00310         # raise e
00311     else:
00312         print "La fonction s'est termin�e correctement"
00313 
00314 
00315 def runWithTimeout(functionToRun, timeout=10):
00316     """
00317     Ex�cute la fonction avec le runner. On illustre ici la modalit�
00318     d'utilisation du Runner.
00319     """
00320     print "runWithTimeout : DEBUT"
00321     runner = Runner(functionToRun)
00322     runner.start()
00323 
00324     # On se fixe un temps au del� duquel on consid�re que la fonction
00325     # est en erreur => on tue le thread (timeout)
00326     runner.wait(timeout)
00327     print "Apr�s runner.timeout(timeout)"
00328     if not runner.isEnded():    
00329         runner.kill()
00330     e = runner.getException()
00331     if e is not None:
00332         print "La fonction s'est termin�e en erreur"
00333         print e
00334         # On peut en fait la relancer
00335         # raise e
00336     else:
00337         print "La fonction s'est termin�e correctement"
00338 
00339     print "runWithTimeout : FIN"
00340     import sys
00341     sys.exit(0)
00342     
00343 
00344 def TEST_Timeout():
00345     #runWithTimeout(function_ok)
00346     #runWithTimeout(function_ok,2)
00347     runWithTimeout(function_with_exception)
00348 
00349     
00350 def TEST_Runner():
00351     runWithRunner(function_ok)
00352     runWithRunner(function_with_exception)
00353     #runWithRunner(infinite_function)
00354 
00355 
00356 def myCallbackFunction():
00357     print "myCallbackFunction: the job is ended"
00358     
00359 
00360 def TEST_runWithCallback():
00361     runner = Runner(function_ok,8)
00362     runner.setCallbackFunction(myCallbackFunction)
00363     runner.start()
00364 
00365     if runner.getException() is not None:
00366         return False
00367 
00368     runnerId = runner.getId()
00369     print "A runner has been started with id="+str(runnerId)
00370     cpt = 0
00371     while ( not runner.isEnded() ):
00372         print "Waiting notification from process "+str(runner.getId())+", step n°"+str(cpt)
00373         time.sleep(0.2)
00374         cpt+=1
00375 
00376     return True
00377 
00378 if __name__ == "__main__":
00379     import unittester
00380     #TEST_PeriodicTimer()
00381     #TEST_Runner()
00382     #TEST_Timeout()
00383     unittester.run("threadhelper","TEST_runWithCallback")
00384 
00385