Skip to content

Autoproxy lifetimes are broken #92

@TheSven73

Description

@TheSven73

I'm trying to take an existing Python library, and allow it to be used remotely over the network. The library creates and returns Python objects which contain C library handles, so AFAIK they cannot be serialized, and need to be auto-proxied.

So far so good. But, I am running into trouble with autoproxy object lifetimes. When an autoproxy proxy handle goes out of scope, the corresponding server object is never released.

Some pytests which I believe should succeed, yet two fail. Edit: python==3.12.3, Pyro5==5.15, pytest==8.2.2

from concurrent.futures import ThreadPoolExecutor

import pytest
from Pyro5.api import Daemon, Proxy, behavior, expose


@expose
class Child:
    def __init__(self, name: str, alive: set[str]):
        self.__name = name
        self.__alive = alive
        alive.add(name)

    def __del__(self):
        self.__alive.remove(self.__name)

    def __repr__(self):
        return self.__name

    def ping(self):
        return f"I am {self.__name}"


_live_objects: set[str]


@expose
@behavior(instance_mode="session", instance_creator=lambda clazz: clazz(_live_objects))
class Parent:
    _pyroDaemon: Daemon

    def __init__(self, alive: set[str]):
        self.__name = f"outer {id(self)}"
        self.__alive = alive
        alive.add(self.__name)

    def __del__(self):
        self.__alive.remove(self.__name)

    def __repr__(self):
        return self.__name

    def ping(self):
        return f"I am {self.__name}"

    def createInstance(self, weak=False):
        i = Child(name="single instance", alive=self.__alive)
        self._pyroDaemon.register(i, weak)
        return i

    def createGenerator(self, weak=False):
        ii = [
            Child(name=f"iterator instance {i}", alive=self.__alive) for i in range(4)
        ]
        for i in ii:
            self._pyroDaemon.register(i, weak)
            yield i


@pytest.fixture
def proxy():
    global _live_objects
    _live_objects = set()
    with ThreadPoolExecutor(max_workers=1) as e, Daemon() as daemon:
        uri = daemon.register(Parent, force=True)
        must_shutdown = False
        _ = e.submit(daemon.requestLoop, lambda: not must_shutdown)
        with Proxy(uri) as proxy1, Proxy(uri) as proxy2:
            yield proxy1, proxy2
        must_shutdown = True
    assert not _live_objects, "some objects were not cleaned up"


# succeeds
def test_proxy_lifetime(proxy):
    p1, p2 = proxy
    assert "outer" in p1.ping()
    assert "outer" in p2.ping()


# fails teardown: AssertionError: some objects were not cleaned up
def test_instance_lifetime(proxy):
    p, _ = proxy
    assert "outer" in p.ping()
    inner = p.createInstance()
    assert "single instance" in inner.ping()


# fails teardown: AssertionError: some objects were not cleaned up
def test_generator_lifetime(proxy):
    p, _ = proxy
    for inner in p.createGenerator():
        assert "iterator instance" in inner.ping()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions