[Twisted-Python] Problems with PB and Jelly...
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 I'm new to twisted, and attempting to use it for networkable gaming; unfortunately I've run into some problems. I have a Perspective <-> Referenceable server/client setup, and am trying to pass objects between them. Following the online docs I've set all the relavent classes to inherit from pb.Copyable + pb.RemoteCopy, and passed them all to pb.setUnjellyableForClass. However, I still get "Module not allowed" exceptions raised in twisted.spread.jelly.py line 523. Looking more closely, I see that modules are not allowed unless passed to SecurityOptions.allowModules(), which is only called from SecurityOptions.allowInstancesOf(), which is not exposed through pb... In short I don't see how the example code on: http://twistedmatrix.com/documents/howto/pb-copyable could possibly work! Is it indeed necessary to call allowInstancesOf() directly? Hopefully I am just missing something... :-) I'm also unsure what to make of the last bullet under "Things To Watch Out For" on the above webpage. It seems unsure whether using __init__ to initialize transferable objects is ok... Must I truly go and hack all my objects to not use __init__, and instead use setCopyableState()? This, especially when combined with the need to inherit from pb.Copyable + pb.RemoteCopy, seems onerous when compared to the minimal interface of pickle. I would like to be able to simple pass objects of arbitrary classes, provided I've registered them as UnJellyable. Hopefully I'm missing something here as well! Thanks in advance, - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+eQVb8EpjZ7/X9bIRAoGmAKDOp/mMXUV0wGG057+CK5Shf75k9ACgu9Cb EuFB62b9vaObg3LZZlJ81jg= =8Dte -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/588326b78898c23564d107c4fa70c2f0.jpg?s=120&d=mm&r=g)
The "Module not allowed" exception means that you need to call setCopierForClass for the class. This maps a server-side class string to a client side class so the Jelly knows what type of object to create on the client. For example: pb.setCopierForClass('MyApp.game.server.door.Door', RemoteDoor) This is usually done at the bottom of the file that the class client-side is defined in. As far the inheritance, you should only to inherit from RemoteCopy for client side objects, not both pb.Copyable and pb.RemoteCopy... The "setCopyableState" idiom for constructing remote classes may seem onerous at first, but its use really is necessary. Distributed objects _are_ different from regular local objects and the requirements for managing them are different too. One of the benefits of PB is its inherent security model - it is far more secure and resistant to hackery than using Pickle. Pickle's internal use of Python's eval function makes it very scary to unserialized Pickled objects on a server... -----Original Message----- From: twisted-python-admin@twistedmatrix.com [mailto:twisted-python-admin@twistedmatrix.com]On Behalf Of Jasper Phillips Sent: Wednesday, March 19, 2003 6:04 PM To: twisted-python@twistedmatrix.com Subject: [Twisted-Python] Problems with PB and Jelly... -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 I'm new to twisted, and attempting to use it for networkable gaming; unfortunately I've run into some problems. I have a Perspective <-> Referenceable server/client setup, and am trying to pass objects between them. Following the online docs I've set all the relavent classes to inherit from pb.Copyable + pb.RemoteCopy, and passed them all to pb.setUnjellyableForClass. However, I still get "Module not allowed" exceptions raised in twisted.spread.jelly.py line 523. Looking more closely, I see that modules are not allowed unless passed to SecurityOptions.allowModules(), which is only called from SecurityOptions.allowInstancesOf(), which is not exposed through pb... In short I don't see how the example code on: http://twistedmatrix.com/documents/howto/pb-copyable could possibly work! Is it indeed necessary to call allowInstancesOf() directly? Hopefully I am just missing something... :-) I'm also unsure what to make of the last bullet under "Things To Watch Out For" on the above webpage. It seems unsure whether using __init__ to initialize transferable objects is ok... Must I truly go and hack all my objects to not use __init__, and instead use setCopyableState()? This, especially when combined with the need to inherit from pb.Copyable + pb.RemoteCopy, seems onerous when compared to the minimal interface of pickle. I would like to be able to simple pass objects of arbitrary classes, provided I've registered them as UnJellyable. Hopefully I'm missing something here as well! Thanks in advance, - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+eQVb8EpjZ7/X9bIRAoGmAKDOp/mMXUV0wGG057+CK5Shf75k9ACgu9Cb EuFB62b9vaObg3LZZlJ81jg= =8Dte -----END PGP SIGNATURE----- _______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Wed, 19 Mar 2003, Sean Riley wrote: Thanks for your response!
I thought this was what pb.SetUnjellableForClass() was for... Inspecting pb.py leads to flavors.py, where I see that setCopierForClass is actually a reference to setUnjellyableForClass. So I'm already doing this -- note that I'm not getting class not allowed exceptions, but module not allowed. Moreover following the logic through to it's culmination in jelly.SecurityOptions.allowTypes(), I still can't see how this could affect which modules are allowed. It looks like I need to call jelly.globalSecurity.allowInstancesOf() directly. Probably in a similar manner to how jelly.SetUnjellyableForClass() calls globalSecuirty.allowTypes(). Actually, this looks like a bug: shouldn't SetUnjellyableForClass() call allowInstancesOf() rather than allowTypes()?
As far the inheritance, you should only to inherit from RemoteCopy for client side objects, not both pb.Copyable and pb.RemoteCopy...
The objects can go both ways, and so it is easiest to set them all to inherit from both. This is what's suggested by the documention, so i figured it shouldn't hurt anything.
I do like the security, and many aspects of the architecture are quite nice! Looking more closely at jelly.py I see that I probably don't need to worry about setCopyableState(), as it seems to do it's business without relying upon __init__, and thus __init__ can still be used traditionally. - -Jasper
iD8DBQE+ej658EpjZ7/X9bIRAn6SAKDQ3sl9QMcFYIU3OoOyh6Sh+AFqOACggT0m PnpcFNBsxqhPk9ioke8picY= =9G/G -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thu, 20 Mar 2003, Jasper Phillips wrote:
I see my error now! I was missing a call to SetUnjellyableForClass() (I should use SetUnjellyableForClassTree() ). The "module not allowed" exception is misleading, and stems from what appears to be an obsolete branch "else" branch of jelly._Unjellier.unjelly(), judging by the fact that it uses the temp variable jelType for something different than the "if" branch. Sorry for all the fuss. :-( - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+elTK8EpjZ7/X9bIRAqcRAJ9M6OcyjpsqZ08axlXHErB9k/ZLzwCg/dTe jLo18sFG5VDnw+YO5E16NxU= =5xdl -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/0f15c04b6acde258bd27586371ae94b1.jpg?s=120&d=mm&r=g)
I'll take responsibility for that one (I wrote those docs :). If the classes behave the same way on both ends, then it can make sense to inherit from both Copyable and RemoteCopy. If objects are supposed to behave differently depending upon whether they are the "home" or the "away" form, then you'll want two classes, where the "home" form is pb.Copyable and the receiving "away" form is pb.RemoteCopy. Often this depends upon whether the object really has a home: if it is just a container for some chunk of state, and doesn't hold any references to other objects, then it doesn't really have a home and you can use the dual-inheritance trick to cut down on some typing. In particular classes which could be replaced by dictionaries without losing any functionality fall into this category. You still have to keep in mind that the setCopyableState method is the point where you get to exercise security. The 'state' provided to this method comes from malicious invaders intent upon compromising your application. Make sure it can deal with whatever evil it is given.
To be precise, the received objects are created with a hack that creates an object of a dummy class, then transforms it into the correct class, then runs setCopyableState to populate the attributes. By doing this, it avoids running the new class' __init__ method altogether. When the object is created by you (by using the class name as a callable), it will run __init__. When it is created in response to a received serialized instance, it will not run __init__ but will run setCopyableState instead. This lets you set up objects differently in the two different situations.
I think you might be right. We have a review of PB scheduled for the PyCon sprint next tuesday.. I've added that code to the list of stuff to be examined. We'll try to clear out all the dead code on that day. cheers, -Brian
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Sat, 22 Mar 2003, Brian Warner wrote:
The way you wrote it in the docs made sense to me! As my objects are treated the same on either side, I chose to use multiple inheritence to avoid tripling the number of classes I needed.
Hmmmm. You're implying that the state can't hold references to other objects? That might explain an exception I'm getting, which I'll describe below. If this is true, is there some easy way around this, or do I need a custom setCopyableState()?
I've since gone and investigated more close what's going on. I've used this hack before, and it was exactly what I was hoping to see. However, I'm sometimes getting an exception when the actual dictionary copying is done, as something other than a dict is being copied into __dict__. At this point the "jelType" is "dereference"... Suddenly, I wonder if this is because I'm using a circular reference? I have a "map" which contains "cells", which contain "links" having a reference to "map"... Damn, it looks like this might be the cuplrit. "reference" jelyTypes are recursively descended into before they are stored, and if a dereference is found before it's stored... some sort of _Dereference object is created? An attempt is then made to copy this into __dict__, and boom. I can think of two ways to avoid this problem: 2 passes, or creating instances and storing a ref to them before recursing to determine their their state. Perhaps this _Dereference is intended to be replaced with a real reference in some sort of 2nd pass? I'll have to look into this more closely latter. At first glance it appears to be something best fixed in twisted itself rather than a local setCopyableState()... at least to this twisted newbie. ;-)
I've since looked at this code more closely as well. The "else" branch in question is most definitely _not_ dead code, but it is confusing. ;-) jelly._Unjellier.unjelly() is used in two manners, which you can see fairly easily by printing out the local variable "jelType". Sometimes this is a "type" name that gets munged into a _unjelly_"type" method call (ie the final "if" branch), and other times "jelType" is a class's local import name, which is parsed into recursive unjelly commands (the "else" branch). Then again, that's what I think is going on. Hard to say, since I'm still getting exceptions out of it I don't quite grasp. ;-( - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+faGY8EpjZ7/X9bIRAq3bAKDOyRV9oB98xDlTlUdx9rgEz1eItgCgx4Yu 7S3uLjnNpZ1JLDNEmMhWT9Y= =+Ilk -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/0f15c04b6acde258bd27586371ae94b1.jpg?s=120&d=mm&r=g)
The state *can* hold references to other objects (as long as those objects are themselves somehow transferrable, like pb.Copyable). I draw a distinction between having or not having references to other objects because data that is standalone makes more sense when transferred to other memory spaces. Imagine your data structures make up a big directed graph. The edges are references, each contained in one object and pointing at another. When you copy one of the objects out to another memory space, you're plucking a node out of the graph and putting it somewhere else. What happens to the edges? It depends upon what flavor the referenced objects inherit from. If they are pb.Referenceable, the edges turn into pb.RemoteReferences, and it's as if the edge-arrows are stretched to run from the object's new location back to the home memory space. If they are pb.Copyable, the reference is followed and the target object copied just like the original object was. If they are neither, you get an InsecureJelly exception. The idea is to prevent you from accidentially copying out objects that you didn't intend to be shared. So if the object points to a lot of other objects, those referents make up an environment. If the environment doesn't come with the object, then that object could be said to have a "home", and then it makes sense to talk about the "home" version of an object versus a copy that lives somewhere "away" from that home. If the object is mostly standalone, then it doesn't matter where the object lives and the home/away distinction is moot.
Which exception is being raised? If it's the InsecureJelly, then you're referencing an object that doesn't inherit from one of the PB flavors. You either need to remove that reference in your getStateToCopy() method (cut the edge-arrow) or you need to make the referenced objects inherit from something like pb.Copyable. If it's something else, let us know (and provide a small test case??) so we can fix it at the sprint.
Yes, the current Jelly code looks for objects that are referenced multiple times in the same jellying call and marks them with "reference" tags. When another reference to the same object is detected, it is jellied with a "dereference" tag that points to the earlier "reference" marker. The "cook", "prepare", and "preserve" methods are used to implement these multiple phases. Circular or recursive references are handled because the reference number is allocated when we start to jelly the object, even though the state is not yet known. This scheme will change on Tuesday. The "reference" tags will go away and be replaced by an implicit marker that is notionally inserted every time we start jellying a new mutable object. The "dereference" tags will then point to these implicit markers. This should improve performance quite a bit, and will pave the way to a combined jelly+banana extension module that should give an enormous speedup (doing everything in C).
Definitely. Twisted should "just handle" arbitrary reference graphs with no problems right now.. the change planned for the PyCon sprint will make it handle them faster and with less on-wire traffic than before.
I've since looked at this code more closely as well. The "else" branch in question is most definitely _not_ dead code, but it is confusing. ;-)
I think _unjelly_instance might be dead code, because I don't see anywhere an "instance" tag could be inserted into the stream. That might be compatibility with an older version of the jelly side, though. Another item on the PB sprint will be to implement proper version markers so this sort of thing can be done properly next time. cheers, -Brian
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Sun, 23 Mar 2003, Brian Warner wrote: Thanks for the response! [snip: pb.Copyable copies all references found]
[snip]
If it's something else, let us know (and provide a small test case??) so we can fix it at the sprint.
Here's the relavent end of the error: File "..\twisted\spread\flavors.py", line 386, in setCopyableState self.__dict__ = state exceptions.TypeError: __dict__ must be set to a dictionary I've included a test case at the end of the message.
The number is allocated earlier on, but it isn't stored for use by "derference" jelTypes until after recursion, as I will attempt to describe: Here are the relavent parts of jelly._Unjellier. Note that the reference is stored after it's value is recursively computed -+ | def _unjelly_reference(self, lst): | refid = lst[0] | exp = lst[1] | o = self.unjelly(exp) <--------+ ref = self.references.get(refid) <--------+ if (ref is None): self.references[refid] = o elif isinstance(ref, NotKnown): ref.resolveDependants(o) self.references[refid] = o else: assert 0, "Multiple references with same ID!" return o This ends up causing the following method to return a _Dereference, rather than a state dictionary that unjelly() would normally return. This then results in the error I list above. def _unjelly_dereference(self, lst): refid = lst[0] print refid #!!! added x = self.references.get(refid) if x is not None: return x der = _Dereference(refid) self.references[refid] = der return der
Sounds good! No point in my suggesting a fix then. ;-) Will a new version of twisted be released along with these changes, or would I have to go through CVS? [snip] Here are 3 files for reproducing the "Unjellying a Circular Reference Bug". Just stick them all in the same place, and start the server then the client. - -Jasper Listing for objectBug.py =-=-=-=-=-=-=-=-=-=-=-=- from twisted.spread import pb class ClassA( pb.Copyable, pb.RemoteCopy ): def __init__( self ): self.ref = ClassB( self ) class ClassB( pb.Copyable, pb.RemoteCopy ): def __init__( self, ref ): self.ref = ref import sys pb.setCopierForClassTree( sys.modules[__name__], pb.Copyable ) Listing for serverBug.py =-=-=-=-=-=-=-=-=-=-=-=- #/usr/bin/env python from twisted.internet import app from twisted.spread import pb from twisted.cred import authorizer import clientBug, objectBug class MyPer( pb.Perspective ): def attached( self, client, identity ): self.client = client return pb.Perspective.attached( self, client, identity ) def perspective_receive( self, obj ): self.client.callRemote( "receive", obj ) class MyService( pb.Service ): perspectiveClass = MyPer def __init__( self, serviceParent, auth ): pb.Service.__init__( self, "MyService", serviceParent, auth ) def startServer(): myApp = app.Application("pbServer") auth = authorizer.DefaultAuthorizer( myApp ) s = MyService( myApp, auth ) s.createPerspective( "player1" ).makeIdentity( "password1" ) myApp.listenTCP( 9000, pb.BrokerFactory( pb.AuthRoot( auth ))) myApp.run( save=0 ) if __name__ == '__main__': startServer() Listing for clientBug.py =-=-=-=-=-=-=-=-=-=-=-=- #/usr/bin/env python from twisted.internet import reactor from twisted.spread import pb import objectBug class MyClient( pb.Referenceable ): def connect( self, ipAddress, port, user, password ): defer = pb.connect( ipAddress, port, user, password, "MyService", client=self, timeout=3 ) defer.addCallback( self.connected ) def connected( self, perspective ): perspective.callRemote( "receive", objectBug.ClassA() ) def remote_receive( self, obj ): print "Object received back" def startClient(): client = MyClient() client.connect( "localhost", 9000, "player1", "password1" ) reactor.run() if __name__ == '__main__': startClient() -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+f45+8EpjZ7/X9bIRAr0yAJ9P2dwozcY478sVL6GKemwYoHAZmwCfYbhT ppjMznAw88wjXItJDczxTVU= =LvPx -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
On Mon, 24 Mar 2003, Jasper Phillips wrote: [talking about twisted.spread.jelly._Unjellier]
I notice that there is a "newjelly.py" in twisted.spread, which appears to have changed the code relavent to the error I was getting. However, I couldn't easily tell from the commit email whether this was finished, or how one would use it... Am I correct in guessing newjelly.py is intended to be renamed to jelly.py, but isn't ready yet? -Jasper PS I've got a kludge to the 1.03 jelly.py and flavors.py that avoids the "storing a _Derefence into a __dict__" error I was getting. If anyone's interested I can post it, although there would be little point if newjelly.py will be ready soon.
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thursday, April 3, 2003, at 05:35 AM, Jasper Phillips wrote:
The way you use it is by replacing the imports at the top of pb.py and flavors.py to say "import jelly as newjelly" or "from newjelly import ...". Please do this and see if your problem is solved. (better yet, contribute a unit test so we can make sure future changes don't break this either...)
The real issue is that this is a protocol-breaking change, and we're not quite prepared to deal with the fallout from that. Brian Warner and I hacked on this at PyCon, and he has some ideas for backwards compatible version negotiation, so we may want to keep the old implementation around anyway. When we figure out if and how we're going to do backwards compatibility, we'll make the changeover. Also, the idea behind this implementation was to simplify things so that some new kinds of optimizations are possible -- the implementation is still really grotty (though it should be functional; I've made the aforementioned change to my pb.py and flavors.py and the PB unit tests pass), and I want to make sure that these optimizations *are* actually made possible, and we haven't just shuffled things around for no reason :-) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.1 (Darwin) iD8DBQE+jB2evVGR4uSOE2wRAmCuAJ4laVyToPx8ybOt0RJP46voWjkUGQCgt99P 1sJ1romG8LZpzKXQD5Pj1Oc= =kuA3 -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thu, 3 Apr 2003, Glyph Lefkowitz wrote:
I just took the shortcut of replacing jelly.py with newjelly.py, as I didn't want to hunt down imports to change. I got several errors, and hence wondered if it was finished, e.g. I get this Traceback when sending from Perspective -> Client ... File "...jelly.py", line 536, in _unjelly_tuple if preTuple.resolvedObject is None: exceptions.AttributeError: _Tuple instance has no attribute 'resolvedObject' After this error I get a similar error (when sending from Client -> Perspective) where a local variable that is set on my Perspective before calling a "remote" client method is later missing when the client calls a "perspective" method. I haven't looks more closely than this though. I posted a test case demonstrating the "circular ref" bug a couple of messages up this thread, but didn't work out a unit test. I'll try to get some time to do that, although I understand twisted uses something other than pyunit so I'm not sure what's involved. A cursory glance at newjelly.py makes me think it can't possibly have the same problem, as "references" and "derefences" now seem to be treated in the same manner, and presumably your unit tests cover simple references. ;-)
Isn't all the Jelly/Unjelly code pretty well encapsulated, so that others needed really care how it works internally?
For what it's worth (after a cursory glance), the reference/dereference cleanup part of your changes looks like a clear improvement, but in understandability and correctness. :-) - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+jCpU8EpjZ7/X9bIRAt99AKCXvM5vAXQhymxTnEfgVq/R0cPPNACeJmo1 7SVWbqQIC1d6YNsTSmrMUPk= =g0Qk -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/56e4cc78ea7fcf3bb37888ebf23bc1f0.jpg?s=120&d=mm&r=g)
On Thu, Apr 03, 2003 at 04:34:22AM -0800, Jasper Phillips wrote:
We use twisted.trial, which presents an interface that's almost identical to pyunit, so if you're familiar with one, working with the other should be fairly easy. That said, I've added a TestCase to test_jelly.py that seems to tickle this bug. :) It's simplified even more from your example, but I think it still catches the essense of the problem. Jp -- 1.79 x 10^24 furlongs per picofortnight - It's not just a good idea, It's the law! -- up 14 days, 14:00, 4 users, load average: 0.04, 0.03, 0.00
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
On Thu, 3 Apr 2003, Jp Calderone wrote:
Ah. Judging from your unit tests, It looks you can just import twisted.trial.unittest as unittest, and existing pyunit tests should just work. D'oh!
Thanks! It was probably a good idea to remove all of the pb baggage from my test case. ;-) -Jasper
![](https://secure.gravatar.com/avatar/588326b78898c23564d107c4fa70c2f0.jpg?s=120&d=mm&r=g)
The "Module not allowed" exception means that you need to call setCopierForClass for the class. This maps a server-side class string to a client side class so the Jelly knows what type of object to create on the client. For example: pb.setCopierForClass('MyApp.game.server.door.Door', RemoteDoor) This is usually done at the bottom of the file that the class client-side is defined in. As far the inheritance, you should only to inherit from RemoteCopy for client side objects, not both pb.Copyable and pb.RemoteCopy... The "setCopyableState" idiom for constructing remote classes may seem onerous at first, but its use really is necessary. Distributed objects _are_ different from regular local objects and the requirements for managing them are different too. One of the benefits of PB is its inherent security model - it is far more secure and resistant to hackery than using Pickle. Pickle's internal use of Python's eval function makes it very scary to unserialized Pickled objects on a server... -----Original Message----- From: twisted-python-admin@twistedmatrix.com [mailto:twisted-python-admin@twistedmatrix.com]On Behalf Of Jasper Phillips Sent: Wednesday, March 19, 2003 6:04 PM To: twisted-python@twistedmatrix.com Subject: [Twisted-Python] Problems with PB and Jelly... -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 I'm new to twisted, and attempting to use it for networkable gaming; unfortunately I've run into some problems. I have a Perspective <-> Referenceable server/client setup, and am trying to pass objects between them. Following the online docs I've set all the relavent classes to inherit from pb.Copyable + pb.RemoteCopy, and passed them all to pb.setUnjellyableForClass. However, I still get "Module not allowed" exceptions raised in twisted.spread.jelly.py line 523. Looking more closely, I see that modules are not allowed unless passed to SecurityOptions.allowModules(), which is only called from SecurityOptions.allowInstancesOf(), which is not exposed through pb... In short I don't see how the example code on: http://twistedmatrix.com/documents/howto/pb-copyable could possibly work! Is it indeed necessary to call allowInstancesOf() directly? Hopefully I am just missing something... :-) I'm also unsure what to make of the last bullet under "Things To Watch Out For" on the above webpage. It seems unsure whether using __init__ to initialize transferable objects is ok... Must I truly go and hack all my objects to not use __init__, and instead use setCopyableState()? This, especially when combined with the need to inherit from pb.Copyable + pb.RemoteCopy, seems onerous when compared to the minimal interface of pickle. I would like to be able to simple pass objects of arbitrary classes, provided I've registered them as UnJellyable. Hopefully I'm missing something here as well! Thanks in advance, - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+eQVb8EpjZ7/X9bIRAoGmAKDOp/mMXUV0wGG057+CK5Shf75k9ACgu9Cb EuFB62b9vaObg3LZZlJ81jg= =8Dte -----END PGP SIGNATURE----- _______________________________________________ Twisted-Python mailing list Twisted-Python@twistedmatrix.com http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Wed, 19 Mar 2003, Sean Riley wrote: Thanks for your response!
I thought this was what pb.SetUnjellableForClass() was for... Inspecting pb.py leads to flavors.py, where I see that setCopierForClass is actually a reference to setUnjellyableForClass. So I'm already doing this -- note that I'm not getting class not allowed exceptions, but module not allowed. Moreover following the logic through to it's culmination in jelly.SecurityOptions.allowTypes(), I still can't see how this could affect which modules are allowed. It looks like I need to call jelly.globalSecurity.allowInstancesOf() directly. Probably in a similar manner to how jelly.SetUnjellyableForClass() calls globalSecuirty.allowTypes(). Actually, this looks like a bug: shouldn't SetUnjellyableForClass() call allowInstancesOf() rather than allowTypes()?
As far the inheritance, you should only to inherit from RemoteCopy for client side objects, not both pb.Copyable and pb.RemoteCopy...
The objects can go both ways, and so it is easiest to set them all to inherit from both. This is what's suggested by the documention, so i figured it shouldn't hurt anything.
I do like the security, and many aspects of the architecture are quite nice! Looking more closely at jelly.py I see that I probably don't need to worry about setCopyableState(), as it seems to do it's business without relying upon __init__, and thus __init__ can still be used traditionally. - -Jasper
iD8DBQE+ej658EpjZ7/X9bIRAn6SAKDQ3sl9QMcFYIU3OoOyh6Sh+AFqOACggT0m PnpcFNBsxqhPk9ioke8picY= =9G/G -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thu, 20 Mar 2003, Jasper Phillips wrote:
I see my error now! I was missing a call to SetUnjellyableForClass() (I should use SetUnjellyableForClassTree() ). The "module not allowed" exception is misleading, and stems from what appears to be an obsolete branch "else" branch of jelly._Unjellier.unjelly(), judging by the fact that it uses the temp variable jelType for something different than the "if" branch. Sorry for all the fuss. :-( - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+elTK8EpjZ7/X9bIRAqcRAJ9M6OcyjpsqZ08axlXHErB9k/ZLzwCg/dTe jLo18sFG5VDnw+YO5E16NxU= =5xdl -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/0f15c04b6acde258bd27586371ae94b1.jpg?s=120&d=mm&r=g)
I'll take responsibility for that one (I wrote those docs :). If the classes behave the same way on both ends, then it can make sense to inherit from both Copyable and RemoteCopy. If objects are supposed to behave differently depending upon whether they are the "home" or the "away" form, then you'll want two classes, where the "home" form is pb.Copyable and the receiving "away" form is pb.RemoteCopy. Often this depends upon whether the object really has a home: if it is just a container for some chunk of state, and doesn't hold any references to other objects, then it doesn't really have a home and you can use the dual-inheritance trick to cut down on some typing. In particular classes which could be replaced by dictionaries without losing any functionality fall into this category. You still have to keep in mind that the setCopyableState method is the point where you get to exercise security. The 'state' provided to this method comes from malicious invaders intent upon compromising your application. Make sure it can deal with whatever evil it is given.
To be precise, the received objects are created with a hack that creates an object of a dummy class, then transforms it into the correct class, then runs setCopyableState to populate the attributes. By doing this, it avoids running the new class' __init__ method altogether. When the object is created by you (by using the class name as a callable), it will run __init__. When it is created in response to a received serialized instance, it will not run __init__ but will run setCopyableState instead. This lets you set up objects differently in the two different situations.
I think you might be right. We have a review of PB scheduled for the PyCon sprint next tuesday.. I've added that code to the list of stuff to be examined. We'll try to clear out all the dead code on that day. cheers, -Brian
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Sat, 22 Mar 2003, Brian Warner wrote:
The way you wrote it in the docs made sense to me! As my objects are treated the same on either side, I chose to use multiple inheritence to avoid tripling the number of classes I needed.
Hmmmm. You're implying that the state can't hold references to other objects? That might explain an exception I'm getting, which I'll describe below. If this is true, is there some easy way around this, or do I need a custom setCopyableState()?
I've since gone and investigated more close what's going on. I've used this hack before, and it was exactly what I was hoping to see. However, I'm sometimes getting an exception when the actual dictionary copying is done, as something other than a dict is being copied into __dict__. At this point the "jelType" is "dereference"... Suddenly, I wonder if this is because I'm using a circular reference? I have a "map" which contains "cells", which contain "links" having a reference to "map"... Damn, it looks like this might be the cuplrit. "reference" jelyTypes are recursively descended into before they are stored, and if a dereference is found before it's stored... some sort of _Dereference object is created? An attempt is then made to copy this into __dict__, and boom. I can think of two ways to avoid this problem: 2 passes, or creating instances and storing a ref to them before recursing to determine their their state. Perhaps this _Dereference is intended to be replaced with a real reference in some sort of 2nd pass? I'll have to look into this more closely latter. At first glance it appears to be something best fixed in twisted itself rather than a local setCopyableState()... at least to this twisted newbie. ;-)
I've since looked at this code more closely as well. The "else" branch in question is most definitely _not_ dead code, but it is confusing. ;-) jelly._Unjellier.unjelly() is used in two manners, which you can see fairly easily by printing out the local variable "jelType". Sometimes this is a "type" name that gets munged into a _unjelly_"type" method call (ie the final "if" branch), and other times "jelType" is a class's local import name, which is parsed into recursive unjelly commands (the "else" branch). Then again, that's what I think is going on. Hard to say, since I'm still getting exceptions out of it I don't quite grasp. ;-( - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+faGY8EpjZ7/X9bIRAq3bAKDOyRV9oB98xDlTlUdx9rgEz1eItgCgx4Yu 7S3uLjnNpZ1JLDNEmMhWT9Y= =+Ilk -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/0f15c04b6acde258bd27586371ae94b1.jpg?s=120&d=mm&r=g)
The state *can* hold references to other objects (as long as those objects are themselves somehow transferrable, like pb.Copyable). I draw a distinction between having or not having references to other objects because data that is standalone makes more sense when transferred to other memory spaces. Imagine your data structures make up a big directed graph. The edges are references, each contained in one object and pointing at another. When you copy one of the objects out to another memory space, you're plucking a node out of the graph and putting it somewhere else. What happens to the edges? It depends upon what flavor the referenced objects inherit from. If they are pb.Referenceable, the edges turn into pb.RemoteReferences, and it's as if the edge-arrows are stretched to run from the object's new location back to the home memory space. If they are pb.Copyable, the reference is followed and the target object copied just like the original object was. If they are neither, you get an InsecureJelly exception. The idea is to prevent you from accidentially copying out objects that you didn't intend to be shared. So if the object points to a lot of other objects, those referents make up an environment. If the environment doesn't come with the object, then that object could be said to have a "home", and then it makes sense to talk about the "home" version of an object versus a copy that lives somewhere "away" from that home. If the object is mostly standalone, then it doesn't matter where the object lives and the home/away distinction is moot.
Which exception is being raised? If it's the InsecureJelly, then you're referencing an object that doesn't inherit from one of the PB flavors. You either need to remove that reference in your getStateToCopy() method (cut the edge-arrow) or you need to make the referenced objects inherit from something like pb.Copyable. If it's something else, let us know (and provide a small test case??) so we can fix it at the sprint.
Yes, the current Jelly code looks for objects that are referenced multiple times in the same jellying call and marks them with "reference" tags. When another reference to the same object is detected, it is jellied with a "dereference" tag that points to the earlier "reference" marker. The "cook", "prepare", and "preserve" methods are used to implement these multiple phases. Circular or recursive references are handled because the reference number is allocated when we start to jelly the object, even though the state is not yet known. This scheme will change on Tuesday. The "reference" tags will go away and be replaced by an implicit marker that is notionally inserted every time we start jellying a new mutable object. The "dereference" tags will then point to these implicit markers. This should improve performance quite a bit, and will pave the way to a combined jelly+banana extension module that should give an enormous speedup (doing everything in C).
Definitely. Twisted should "just handle" arbitrary reference graphs with no problems right now.. the change planned for the PyCon sprint will make it handle them faster and with less on-wire traffic than before.
I've since looked at this code more closely as well. The "else" branch in question is most definitely _not_ dead code, but it is confusing. ;-)
I think _unjelly_instance might be dead code, because I don't see anywhere an "instance" tag could be inserted into the stream. That might be compatibility with an older version of the jelly side, though. Another item on the PB sprint will be to implement proper version markers so this sort of thing can be done properly next time. cheers, -Brian
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Sun, 23 Mar 2003, Brian Warner wrote: Thanks for the response! [snip: pb.Copyable copies all references found]
[snip]
If it's something else, let us know (and provide a small test case??) so we can fix it at the sprint.
Here's the relavent end of the error: File "..\twisted\spread\flavors.py", line 386, in setCopyableState self.__dict__ = state exceptions.TypeError: __dict__ must be set to a dictionary I've included a test case at the end of the message.
The number is allocated earlier on, but it isn't stored for use by "derference" jelTypes until after recursion, as I will attempt to describe: Here are the relavent parts of jelly._Unjellier. Note that the reference is stored after it's value is recursively computed -+ | def _unjelly_reference(self, lst): | refid = lst[0] | exp = lst[1] | o = self.unjelly(exp) <--------+ ref = self.references.get(refid) <--------+ if (ref is None): self.references[refid] = o elif isinstance(ref, NotKnown): ref.resolveDependants(o) self.references[refid] = o else: assert 0, "Multiple references with same ID!" return o This ends up causing the following method to return a _Dereference, rather than a state dictionary that unjelly() would normally return. This then results in the error I list above. def _unjelly_dereference(self, lst): refid = lst[0] print refid #!!! added x = self.references.get(refid) if x is not None: return x der = _Dereference(refid) self.references[refid] = der return der
Sounds good! No point in my suggesting a fix then. ;-) Will a new version of twisted be released along with these changes, or would I have to go through CVS? [snip] Here are 3 files for reproducing the "Unjellying a Circular Reference Bug". Just stick them all in the same place, and start the server then the client. - -Jasper Listing for objectBug.py =-=-=-=-=-=-=-=-=-=-=-=- from twisted.spread import pb class ClassA( pb.Copyable, pb.RemoteCopy ): def __init__( self ): self.ref = ClassB( self ) class ClassB( pb.Copyable, pb.RemoteCopy ): def __init__( self, ref ): self.ref = ref import sys pb.setCopierForClassTree( sys.modules[__name__], pb.Copyable ) Listing for serverBug.py =-=-=-=-=-=-=-=-=-=-=-=- #/usr/bin/env python from twisted.internet import app from twisted.spread import pb from twisted.cred import authorizer import clientBug, objectBug class MyPer( pb.Perspective ): def attached( self, client, identity ): self.client = client return pb.Perspective.attached( self, client, identity ) def perspective_receive( self, obj ): self.client.callRemote( "receive", obj ) class MyService( pb.Service ): perspectiveClass = MyPer def __init__( self, serviceParent, auth ): pb.Service.__init__( self, "MyService", serviceParent, auth ) def startServer(): myApp = app.Application("pbServer") auth = authorizer.DefaultAuthorizer( myApp ) s = MyService( myApp, auth ) s.createPerspective( "player1" ).makeIdentity( "password1" ) myApp.listenTCP( 9000, pb.BrokerFactory( pb.AuthRoot( auth ))) myApp.run( save=0 ) if __name__ == '__main__': startServer() Listing for clientBug.py =-=-=-=-=-=-=-=-=-=-=-=- #/usr/bin/env python from twisted.internet import reactor from twisted.spread import pb import objectBug class MyClient( pb.Referenceable ): def connect( self, ipAddress, port, user, password ): defer = pb.connect( ipAddress, port, user, password, "MyService", client=self, timeout=3 ) defer.addCallback( self.connected ) def connected( self, perspective ): perspective.callRemote( "receive", objectBug.ClassA() ) def remote_receive( self, obj ): print "Object received back" def startClient(): client = MyClient() client.connect( "localhost", 9000, "player1", "password1" ) reactor.run() if __name__ == '__main__': startClient() -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+f45+8EpjZ7/X9bIRAr0yAJ9P2dwozcY478sVL6GKemwYoHAZmwCfYbhT ppjMznAw88wjXItJDczxTVU= =LvPx -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
On Mon, 24 Mar 2003, Jasper Phillips wrote: [talking about twisted.spread.jelly._Unjellier]
I notice that there is a "newjelly.py" in twisted.spread, which appears to have changed the code relavent to the error I was getting. However, I couldn't easily tell from the commit email whether this was finished, or how one would use it... Am I correct in guessing newjelly.py is intended to be renamed to jelly.py, but isn't ready yet? -Jasper PS I've got a kludge to the 1.03 jelly.py and flavors.py that avoids the "storing a _Derefence into a __dict__" error I was getting. If anyone's interested I can post it, although there would be little point if newjelly.py will be ready soon.
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thursday, April 3, 2003, at 05:35 AM, Jasper Phillips wrote:
The way you use it is by replacing the imports at the top of pb.py and flavors.py to say "import jelly as newjelly" or "from newjelly import ...". Please do this and see if your problem is solved. (better yet, contribute a unit test so we can make sure future changes don't break this either...)
The real issue is that this is a protocol-breaking change, and we're not quite prepared to deal with the fallout from that. Brian Warner and I hacked on this at PyCon, and he has some ideas for backwards compatible version negotiation, so we may want to keep the old implementation around anyway. When we figure out if and how we're going to do backwards compatibility, we'll make the changeover. Also, the idea behind this implementation was to simplify things so that some new kinds of optimizations are possible -- the implementation is still really grotty (though it should be functional; I've made the aforementioned change to my pb.py and flavors.py and the PB unit tests pass), and I want to make sure that these optimizations *are* actually made possible, and we haven't just shuffled things around for no reason :-) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.1 (Darwin) iD8DBQE+jB2evVGR4uSOE2wRAmCuAJ4laVyToPx8ybOt0RJP46voWjkUGQCgt99P 1sJ1romG8LZpzKXQD5Pj1Oc= =kuA3 -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Thu, 3 Apr 2003, Glyph Lefkowitz wrote:
I just took the shortcut of replacing jelly.py with newjelly.py, as I didn't want to hunt down imports to change. I got several errors, and hence wondered if it was finished, e.g. I get this Traceback when sending from Perspective -> Client ... File "...jelly.py", line 536, in _unjelly_tuple if preTuple.resolvedObject is None: exceptions.AttributeError: _Tuple instance has no attribute 'resolvedObject' After this error I get a similar error (when sending from Client -> Perspective) where a local variable that is set on my Perspective before calling a "remote" client method is later missing when the client calls a "perspective" method. I haven't looks more closely than this though. I posted a test case demonstrating the "circular ref" bug a couple of messages up this thread, but didn't work out a unit test. I'll try to get some time to do that, although I understand twisted uses something other than pyunit so I'm not sure what's involved. A cursory glance at newjelly.py makes me think it can't possibly have the same problem, as "references" and "derefences" now seem to be treated in the same manner, and presumably your unit tests cover simple references. ;-)
Isn't all the Jelly/Unjelly code pretty well encapsulated, so that others needed really care how it works internally?
For what it's worth (after a cursory glance), the reference/dereference cleanup part of your changes looks like a clear improvement, but in understandability and correctness. :-) - -Jasper -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.6 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE+jCpU8EpjZ7/X9bIRAt99AKCXvM5vAXQhymxTnEfgVq/R0cPPNACeJmo1 7SVWbqQIC1d6YNsTSmrMUPk= =g0Qk -----END PGP SIGNATURE-----
![](https://secure.gravatar.com/avatar/56e4cc78ea7fcf3bb37888ebf23bc1f0.jpg?s=120&d=mm&r=g)
On Thu, Apr 03, 2003 at 04:34:22AM -0800, Jasper Phillips wrote:
We use twisted.trial, which presents an interface that's almost identical to pyunit, so if you're familiar with one, working with the other should be fairly easy. That said, I've added a TestCase to test_jelly.py that seems to tickle this bug. :) It's simplified even more from your example, but I think it still catches the essense of the problem. Jp -- 1.79 x 10^24 furlongs per picofortnight - It's not just a good idea, It's the law! -- up 14 days, 14:00, 4 users, load average: 0.04, 0.03, 0.00
![](https://secure.gravatar.com/avatar/959f1f67ba8219ff01302f9aa24bcfe3.jpg?s=120&d=mm&r=g)
On Thu, 3 Apr 2003, Jp Calderone wrote:
Ah. Judging from your unit tests, It looks you can just import twisted.trial.unittest as unittest, and existing pyunit tests should just work. D'oh!
Thanks! It was probably a good idea to remove all of the pb baggage from my test case. ;-) -Jasper
participants (5)
-
Brian Warner
-
Glyph Lefkowitz
-
Jasper Phillips
-
Jp Calderone
-
Sean Riley