quick search:
 

Zope and Threads

Submitted by: runyaga
Last Edited: 2005-01-20

Category: Product Development

Average rating is: 0.0 out of 5 (0 ratings)

Description:
Here is a quick intro (probably a bit long for a recipe)
into Threading and Zope. In summary, Don't. Use ZEO.


Source (Text):
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()

Explanation:
inlined comments. many thanks to Shane Hathaway,
Dieter Mauer, Steve Alexander, and Kapil. The above
is not the only way to do things. In Zope there are
a variety of ways and the above may not be the best
way. But are a result of my (painful) experiences.

NOTE: in real life I was not aborting/committing my
transactions and my RDBMS connections were run in
isolation as a result. thus not seeing changes
people were making to the database.

Kapil suggested using ZEO initially but I wanted to
get Threads to work and I didnt want the additional
burdon of setting up more monitoring services and
have daemons run.

After a few days of pain. and also realizing that
doing stuff like Report generation in-process is
stupid and should be moved out-of-process and
SteveA pointing out the obvious again (save your
self grief and do it in ZEO) - I have the system
running in 20 minutes. *sigh*.

Again thanks to everyone. ZEO is really the bee-knees.
http://www.zope.org/Members/dshaw/AdvancedSiteSetup is
the best tutorial/howto I have found to get ZEO
running for newbies.


Comments:

Need to change run to start by lubyliao - 2005-01-20
Need to change _thread.run() to _thread.start(), 
otherwise no new thread will be created.

cheers, Luby Liao