[Twisted-Python] Unjellying and circular references?

Jasper Phillips jasperisjaded at gmail.com
Wed Dec 16 03:21:22 MST 2020


On Fri, Dec 11, 2020 at 6:08 AM Jean-Paul Calderone <
exarkun at twistedmatrix.com> wrote:

> On Fri, Dec 11, 2020 at 8:19 AM Jasper Phillips <jasperisjaded at gmail.com>
> wrote:
>
>> I'm using perspective broker to transfer objects in a networked game,
>> which I'm having trouble unjellying -- the remote versions wind up with
>> dangling twisted.persisted.crefutil._Dereference instances, so don't match
>> the original objects.
>>
>
>> I'm seeing this for objects that have circular references to each other.
>> I've refactored things to mostly avoid circular references and sidestep
>> this, but have one remaining case where I find circular references mean
>> clearer code that I'm reluctant to refactor.
>>
>> Is there some trick I'm missing to avoid _Dereferences?
>>
>
> No, it's supposed to Just Work™ so you've found a bug in some part of the
> implementation.  If you can produce a minimal reproducing example then it
> may be worth a bug report.  But after that, I'd suggest investigating HTTP
> or AMP as a replacement for PB.
>

Here's a test case demonstrating the bug:

import sys
from twisted.persisted.crefutil import _Dereference
from twisted.spread             import pb, jelly

class RemoteCopyable( pb.Copyable, pb.RemoteCopy ):  pass
jelly.globalSecurity.allowInstancesOf( RemoteCopyable )
pb.setCopierForClassTree( sys.modules[__name__], pb.Copyable )

if __name__ == '__main__':
    # circular object ref
    cell           = RemoteCopyable()
    cell.link      = RemoteCopyable()
    cell.link.cell = cell

    # Mimic sending across network
    broker         = pb.Broker()
    serializedCell = broker.serialize( cell )
    remoteCell     = broker.unserialize( serializedCell )

    print( _Dereference is type(remoteCell.link.cell) )

This bug stems from twisted.spread.flavors.RemoteCopy.setCopyableState(),
which works fine for Python 2! But the UTF fix for Python 3 broke circular
references. It looks like this:

def setCopyableState(self, state):
    if _PY3:
        state = {x.decode('utf8') if isinstance(x, bytes)
                 else x:y for x,y in state.items()}
    self.__dict__ = state

Here is a simple tweak that fixes the problem:

def setCopyableState(self, state):
    if _PY3:
        for x, y in list(state.items()):
            if isinstance(x, bytes):
                del state[x]
                state[x.decode('utf8')] = y
    self.__dict__ = state


Basically the reference unwinding that takes place in
twisted.spread.jelly._Unjellier._unjelly_reference()'s
call to ref.resolveDependants() relies upon setCopyableStates()'s passed in
state being used directly, such that all matching references' __dict__
point to the same object.

Hope this helps clarify. Is there some more formal location than this list
that you'd like a bug report filed?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: </pipermail/twisted-python/attachments/20201216/d8cc8dd5/attachment-0001.htm>


More information about the Twisted-Python mailing list