Mike

I don't think that you can rely on the threadsafety of these functions. Even 
if they are threadsafe in C Python (which I doubt that 'set' is), the locking 
in Jython in more fine grained and would likely catch you out.

I would suggest that you should routinely wrap shared datamodels like these in 
thread locks to be certain about things. 

I would also suggest that a small change to the Value class would make it 
possible for client code to subclass it, which might make it more flexible.

Here is my suggestion, bare in mind that I have not tested the thread locking 
code beyond making sure that it runs :-)

Regards

Richard


#!/usr/bin/python

from threading import Lock, RLock

class Locker(object):
    def __init__(self,lock):
        self._lock = lock

    def __call__(self):
        lock = self._lock
        def wrap(f):
            def newFunction(*args, **kw):
                lock.acquire()
                try:
                    return f(*args, **kw)
                finally:
                    lock.release()
            return newFunction
        return wrap

write_lock = Locker(Lock())
read_lock = Locker(RLock())

class ConcurrentUpdate(Exception): pass

class Value(object):
    def __init__(self, version, value,store,key, *tup, **kw):
        self.version = version
        self.value = value
        self.store = store
        self.key = key
        self.tup = tup
        self.kw = kw

    def __repr__(self):
        return "Value"+repr((self.version,self.value))

    def set(self, value):
        self.value = value

    def commit(self):
        self.store.set(self.key, self)

    def clone(self):
        return self.__class__(self.version, 
self.value,self.store,self.key,*self.tup, **self.kw)

class Store(object):
    def __init__(self, value_class=Value):
        self.store = {}
        self.value_class = value_class

    @read_lock()
    def get(self, key):
        return self.store[key].clone()

    @write_lock()
    def set(self, key, value):
        if not (self.store[key].version > value.version):
            self.store[key] = self.value_class(value.version+1, value.value, 
self, key)
            value.version= value.version+1
        else:
            raise ConcurrentUpdate

    @read_lock()
    def using(self, key):
        try:
            return self.get(key)
        except KeyError:
            self.store[key] = self.value_class(0, None,self,key)
            return self.get(key)

    def dump(self):
        for k in self.store:
            print k, ":", self.store[k]

S = Store()
greeting = S.using("hello")
print repr(greeting.value)
greeting.set("Hello World")
greeting.commit()
print greeting
S.dump()
# ------------------------------------------------------
par = S.using("hello")
par.set("Woo")
par.commit()
# ------------------------------------------------------
print greeting
S.dump()
# ------------------------------------------------------
greeting.set("Woo")
try:
    greeting.commit()
except ConcurrentUpdate:
    print "Received ConcurrentUpdate exception"

print repr(greeting), repr(greeting.value)

S.dump()



On Saturday 08 December 2007, Michael Sparks wrote:
> I've just posted this message on comp.lang.python fishing for comments, but 
> I'll post it here in case there's any feedback this way :-)
> 
> (apologies for any dupes people get - I don't *think* python-uk & pyconuk 
are 
> strict subsets)
> 
> I'm interested in writing a simple, minimalistic, non persistent (at this
> stage) software transactional memory (STM) module. The idea being it should
> be possible to write such a beast in a way that can be made threadsafe fair
> easily.
> 
> For those who don't know, STM is a really fancy way of saying variables
> with version control (as far as I can tell :-) designed to enable threadsafe
> shared data.
> 
> I'm starting with the caveat here that the following code is almost
> certainly not threadsafe (not put any real thought into that as yet),
> and I'm interested in any feedback on the following:
> 
>    * Does the API look simple enough?
>    * Are there any glaring mistakes in the code ? (It's always harder to see
>      your own bugs)
>    * What key areas appear least threadsafe, and any general suggestions
>      around that.
> 
> If I get no feedback I hope this is of interest. Since these things get
> archived, if you're reading this a month, 6 months, a year or more from
> now, I'll still be interested in feedback...
> 
> OK, API.
> 
> First of all we need to initialise the store:
> 
>     S = Store()
> 
> We then want to get a value from the store such that we can use the value,
> and do stuff with it:
> 
>     greeting = S.using("hello")
> 
> Access the value:
> 
>     print repr(greeting.value)
> 
> Update the value:
> 
>     greeting.set("Hello World")
> 
> Commit the value back to the store:
> 
>     greeting.commit()
> 
> If you have concurrent updates of the same value, the following exception
> gets thrown:
>     ConcurrentUpdate
> 
> cf:
>     >>> S = Store()
>     >>> greeting = S.using("hello")
>     >>> par = S.using("hello")
>     >>> greeting.set("Hello World")
>     >>> par.set("Woo")
>     >>> greeting.commit()
>     >>> par.commit()
>     Traceback (most recent call last):
>       File "<stdin>", line 1, in <module>
>       File "<stdin>", line 12, in commit
>       File "<stdin>", line 11, in set
>     __main__.ConcurrentUpdate
> 
> That's pretty much the simplest API I can come up with. (I've tried a few
> others but didn't really like them)
> 
> The way this works is we have a Store that manages Values. (perhaps a better
> name may be variables to avoid clashing with pythons parlance of labels and
> values?) 
> 
> Anyhow, you can ask the store for Value, which it will give you. The Value
> knows what it's called and where it's from, so when you tell it to commit,
> it can try to do so. The little detail here is you get a copy of the
> Value not the stored Value. (This is to avoid accidental concurrent update
> of the same actual object)
> 
> As a result, Store looks like this:
> 
> class Store(object):
>     def __init__(self):
>         self.store = {}
> 
>     def get(self, key):
>         return self.store[key].clone()
> 
>     def set(self, key, value):
>         if not (self.store[key].version > value.version):
>             self.store[key] = Value(value.version+1, value.value, self, key)
>             value.version= value.version+1
>         else:
>             raise ConcurrentUpdate
> 
>     def using(self, key):
>         try:
>             return self.get(key)
>         except KeyError:
>             self.store[key] = Value(0, None,self,key)
>             return self.get(key)
> 
>     def dump(self):
>         for k in self.store:
>             print k, ":", self.store[k]
> 
> You'll note that the set method is the greatest candidate for any possible
> race hazard here - though I can see a possible boundary issue in "using".
> (I think :-)
> 
> Otherwise I think the above code is relatively straightforward. You'll note
> that this API allows this:
> 
>     greeting.set("Hello")
>     greeting.commit()
>     greeting.set("Hello World")
>     greeting.commit()
>     greeting.set("Hello World. Game")
>     greeting.commit()
>     greeting.set("Hello World. Game Over")
>     greeting.commit()
> 
> The other class is value that looks like this:
> 
> class Value(object):
>     def __init__(self, version, value,store,key):
>         self.version = version
>         self.value = value
>         self.store = store
>         self.key = key
> 
>     def __repr__(self):
>         return "Value"+repr((self.version,self.value,self.store,self.key))
> 
>     def set(self, value):
>         self.value = value
> 
>     def commit(self):
>         self.store.set(self.key, self)
> 
>     def clone(self):
>         return Value(self.version, self.value,self.store,self.key)
> 
> To me this looks like a pretty complete minimalistic thing, which does seem
> to work OK, but I'm interested in the three points I mention above - if
> anyone is willing to comment - specifcally:
> 
>    * Does the API look simple enough?
>    * Are there any glaring mistakes in the code ? (It's always harder to see
>      your own bugs)
>    * What key areas appear least threadsafe, and any general suggestions
>      around that.
> 
> Full code below.
> 
> Many thanks for any comments in advance,
> 
> 
> Michael
> --
> Michael Sparks, Kamaelia Project Lead
> http://kamaelia.sourceforge.net/Developers/
> http://yeoldeclue.com/blog
> 
> #!/usr/bin/python
> 
> class ConcurrentUpdate(Exception): pass
> 
> class Value(object):
>     def __init__(self, version, value,store,key):
>         self.version = version
>         self.value = value
>         self.store = store
>         self.key = key
> 
>     def __repr__(self):
>         return "Value"+repr((self.version,self.value))
> 
>     def set(self, value):
>         self.value = value
> 
>     def commit(self):
>         self.store.set(self.key, self)
> 
>     def clone(self):
>         return Value(self.version, self.value,self.store,self.key)
> 
> class Store(object):
>     def __init__(self):
>         self.store = {}
> 
>     def get(self, key):
>         return self.store[key].clone()
> 
>     def set(self, key, value):
>         if not (self.store[key].version > value.version):
>             self.store[key] = Value(value.version+1, value.value, self, key)
>             value.version= value.version+1
>         else:
>             raise ConcurrentUpdate
> 
>     def using(self, key):
>         try:
>             return self.get(key)
>         except KeyError:
>             self.store[key] = Value(0, None,self,key)
>             return self.get(key)
> 
>     def dump(self):
>         for k in self.store:
>             print k, ":", self.store[k]
> 
> S = Store()
> greeting = S.using("hello")
> print repr(greeting.value)
> greeting.set("Hello World")
> greeting.commit()
> # ------------------------------------------------------
> print greeting
> S.dump()
> # ------------------------------------------------------
> par = S.using("hello")
> par.set("Woo")
> par.commit()
> # ------------------------------------------------------
> print greeting
> S.dump()
> # ------------------------------------------------------
> greeting.set("Woo")
> greeting.commit()
> 
> print repr(greeting), repr(greeting.value)
> 
> S.dump()
> _______________________________________________
> pyconuk mailing list
> [EMAIL PROTECTED]
> http://mail.python.org/mailman/listinfo/pyconuk
> 
> 



-- 
QinetiQ                                  
B009 Woodward Building
St. Andrews Road
Malvern
Worcs WR14 3PS
Jabber: [EMAIL PROTECTED]
PGPKey: http://pgp.mit.edu:11371/pks/lookup?op=get&search=0xA7DA9FD9
Key fingerprint = D051 A121 E7C3 485F 3C0E  1593 ED9E D868 A7DA 9FD9

Attachment: signature.asc
Description: This is a digitally signed message part.

_______________________________________________
python-uk mailing list
python-uk@python.org
http://mail.python.org/mailman/listinfo/python-uk

Reply via email to