Threads inside of Zope # More information can be found in: # $ZOPE/lib/python/ZODB/Connection.py # $ZOPE/lib/python/Zope/App/startup.py # Here are a collection of a few pointers regarding # using threads inside of Zope. In summary: Don't # use them; use ZEO if at all possible. If you *must* # use Threads here are some examples of Do's and Dont's. # Case 1. # On first inspection you may thing to inherient from # both Acquisition.Implicit and Threading.thread # Implicit is a ExtensionClass and the threading module # expects self to be a instance. So we cant simply call # Thread.__init__(self). from Acquisition import Implicit from threading import Thread class ReadOnlyUglyZopeThread(Implicit, Thread): """ This is doable but you need to know the limitations """ _Thread_init_ = Thread.__init__ def __init__(self): _Thread_init_() def run(self): # you have a acquisition context # but you *can not* write anything to ZODB # in this example because you are in a different # thread as Zope TransactionManager which controls # ability writes to the ZODB. print self.Control_Panel.getId() get_transaction().commit() # get_transaction() returns the Transaction Manager # and since ZPublisher does this for you but you are # inside a different thread (and doing things manually) # yourself and already been given a Acquisition Context # you really need to ensure the end of the transaction. #external method that launches thread def extROThread(self): _thread=ReadOnlyUglyZopeThread().__of__(self) # instantiate the ReadOnlyZopeThread and wrap it with a # AcquisitionWrapper so the instance will have a connection to # ZODB. _thread.run() # run thread and continue execution. # Case 2. # The best thing is to get rid of Acquisition. And have the Zope # thread open up a connection to the ZODB itself and manage all # the things itself. from threading import Thread from ZPublisher.HTTPRequest import HTTPRequest from ZPublisher.HTTPResponse import HTTPResponse from ZPublisher.BaseRequest import RequestContainer class ZopeThread(Thread): """ Best practice - dont inherient from Acquisition Manage connections and transactions yourself. Blantantly rip off of code Kapil Thangavelu sent me. """ def __init__(self): Thread.__init__(self) def run(self): try: from Zope import DB conn = DB.open() root = conn.root() app = root['Application'] ctx = self.getContext(app) #actual work, ctx is the root of your Zope Application print ctx.Control_Panel.getId() except: import sys fh = open(error_file, 'a') ec, e, tb = sys.exc_info() print >> fh, "%s An Error Occured"%(str(DateTime())) print >> fh, "%s: %s"%(str(ec), str(e)) traceback.print_tb(tb, file=fh) fh.flush() fh.close() try: get_transaction().abort() #you could commit if you had done writes conn.close() #by closing the database connection you are #implicitly aborting the transaction, #get_transaction().abort() except: pass def getContext(self, app): resp = HTTPResponse(stdout=None) env = { 'SERVER_NAME':'localhost', 'SERVER_PORT':'8080', 'REQUEST_METHOD':'GET' } req = HTTPRequest(None, env, resp) return app.__of__(RequestContainer(REQUEST = req)) #external method that launches thread def ext_Thread(self): _thread=ZopeThread() _thread.run() #External Method completes while thread is runing #Threads in Zope are really nasty. In fact Threads themselves #are ugly business but especially when there are so many #implementation details as well as you have to understand #the interactions of the objects you are using from #inside the thread. #The most important part is that if you are reusing Zope #threads that you must commit/abort a transaction or the #Application thread will be put back into the pool in a #inconsistent transaction state. This leads to all sorts #of surprises. # Case 3. #The *best* practice is not to use Threads but to leverage #the ZODB and ZEO. We opted for this solution where we #put pickled instances on the file system and in the ZEO #client we unpickle and create a connection to Zope, #the pickles instance has the path in which we need to #be executed: def process_reports(app): import Reports obj=nextReport() #unpickled instance from FS app._p_jar.sync() #see changes in ZODB klass = getattr(Reports, obj.kwargs['typeOfReport']) try: context=app.unrestrictedTraverse(obj.kwargs['path']) # a begin() is a implicit abort() if a previous trx # is outstanding in the thread. get_transaction().begin() report = klass(*obj.args, **obj.kwargs).__of__(context) pdf = ReportGen().createReport(report()) except: filename = obj.kwargs['filename'] t,v,tb = sys.exc_info() formatted_tb = traceback.format_tb(tb) formatted = traceback.format_exception_only(t,v) error_msg = formatted[-1] error_file = open(filename[:-4]+'.txt','w') error_file.write(error_msg) error_file.write('\n\n') error_file.writelines(formatted_tb) error_file.write('\n%s\n' % str(obj.__dict__)) error_file.close() def begin(): storage=ZEO.ClientStorage.ClientStorage((host, port), name='Report Server') db=DB(storage) connection=db.open() root=connection.root() app=root['Application'] ctx=getContext(app) process_reports(ctx) if __name__=='__main__': begin()