SV: SV: [ZODB-Dev] Flush
Shane Hathaway
shane@zope.com
Fri, 01 Feb 2002 13:35:45 -0500
Magnus Heino wrote:
> Ive got a persistent object being published by ZPublisher. This object has a
> start method that does start_new_thread(self.method, ()). self.method does
> not terminate before a stop method is called. While running, it performs
> come calculations. This way I can view what the thread is doing, by browsing
> with ZPublisher. However, it seems as if the object is flushed from memory,
> leaving a thread running... later on when I call some method on the object,
> it is loaded from disk into memory again. In setstate, I look at a attribute
> self.__isRunning and calls start_new_thread(self.method, ()) if it was
> running when before. This way the thread will start if it was running when
> the app was closed. But also if the object was flushed... :-P
Whoa, that won't work. Under the current ZODB model, every thread has
its own copy of objects. When you start a new thread, you have to load
new copies. Otherwise you'll get rampant race conditions.
I guess there is some long-running operation you want to perform on the
object every time it's loaded? Note that ZODB reserves the right to
unload objects as frequently as it needs; in fact, with the cache
optimizations Toby has proposed, I think objects will be unloaded more
frequently than today.
I'm going to assume there is some long-running operation you want to
perform only once on the object, and that if the operation hasn't been
performed, __setstate__ will launch a thread to do it. Here's one way
to write it for Zope (untested):
class MyClass (Persistent):
_completed = 0
_working = 0
def beforeWork(self):
if self._working:
# Another thread is working on it.
return 0
self._working = 1
return 1
def work(self):
# Do work and change self
# Indicate the work is completed
self._completed = 1
self._working = 0
def __setstate__(self, data):
MyClass.inheritedAttribute('__setstate__')(self, data)
if not self._completed and not self._working:
start_new_thread(traverseAndCall,
(self.getPhysicalPath(), 'beforeWork', 'work'))
def traverseAndCall(path, method_name_1, method_name_2):
import Zope
get_transaction().begin()
get_transaction().note('/'.join(path) + '/' + method_name_1)
app = Zope.app()
ob = app.restrictedTraverse(path)
m = getattr(ob, method_name_1)
if not m():
return # Another thread is working on it.
get_transaction().commit() # Notify that work has started
get_transaction().begin()
get_transaction().note('/'.join(path) + '/' + method_name_2)
m = getattr(ob, method_name_2)
m()
get_transaction().commit() # Commit the work
You will most likely have to adjust that quite a bit, but effectively
the second thread has its own copy of the database and it tries to
prevent more than one thread from performing the work. This works even
if you mix in ZEO, though there is still a race condition. It could be
fixed, but this is just an example.
Shane