Yet another attempt to plug M2Crypto.SSL.Connection.Connection

Michael Dunstan <[email protected]>
Newsgroups gmane.comp.python.cryptography
Message-ID <[email protected]>
Hi all,

Reading "M2Crypto woes"
http://www.artima.com/weblogs/viewpost.jsp?thread=95863 reminded me
that I should go back and see what happened with that patch that in
some fluke just happened to work for me.

Seems that this still exists even on the 0.15 branch. Okay - time to
take a closer look then. Attached is a trivial example demonstrating
the memory leak. When you run this it prints something like:

[<__main__.Leaky instance at 0x6de50>]

Meaning that we have an instance of the Leaky class that is now marked
as garbage and not collected. But the other two instances of non leaky
classes are not present. They have been garbage collected. A class with
the combination of assignment of a bound method and use of __del__
creates a cycle that can not be garbage collected. Well, that's how I'm
interpreting the behaviour I'm seeing. (With python 2.3.4.)

So what to do about this in the case of
M2Crypto.SSL.Connection.Connection? (Without resorting to wrapping
read/write with an if statement.) One way would be to provide a
different class for the different behaviour rather than use
setblocking(). For example:

class Connection:
     """A blocking SSL connection."""
     sendall = send = write = _write_bio
     recv = read = _read_bio

class NonBlockingConnection(Connection):
     """A non blocking SSL connection."""
     send = write = _write_nbio
     recv = read = _read_nbio

(I don't pretend to understand why sendall is not affected by the
different modes. Should it even exist for NonBlockingConnection?)

Not sure how that fits into the existing framework though.

Michael.
leak.py (application/octet-stream, 1.5 KB)
"""Example of leaking class instances when assinging bound methods and
providing a __dell__ method."""

class Leaky:

    def __init__(self):
        self.assinged_bound_method = self.bound_method

    def bound_method(self):
        pass

    def __del__(self):
        pass

class NonLeakyNoAssignmentOfBoundMethod:

    def __init__(self):
        pass

    def bound_method(self):
        pass

    def __del__(self):
        pass

class NonLeakyNo__del__Statement:

    def __init__(self):
        self.assinged_bound_method = self.bound_method

    def bound_method(self):
        pass

class NonLeakyClassTimeBinding:

    def __init__(self):
        pass

    def bound_method(self):
        pass

    # XXX "Class time binding"? What's that? I'm not sure what the
    # real term is. But I'm trying to describe just when the next
    # assignement is made:

    another_bound_method = bound_method

    def __del__(self):
        pass


class NonLeakyDerived(NonLeakyClassTimeBinding):

    def __init__(self):
        pass

    def yet_another_bound_method(self):
        pass

    another_bound_method = yet_another_bound_method

    def __del__(self):
        pass

def test():
    """Create instances of classes in a new scope."""
    leak = Leaky()
    non_leak_no_assignment_of_bound_method = NonLeakyNoAssignmentOfBoundMethod()
    non_leak_no__del__statement = NonLeakyNo__del__Statement()
    non_leak_class_time_binding = NonLeakyClassTimeBinding()
    non_leak_derived = NonLeakyDerived()

test()

import gc
gc.collect()
print gc.garbage
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.