Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to catch exceptions in remote due to possible bug in protocol._handle_ctxexit #564

Open
michael-a-schmidt opened this issue Oct 18, 2024 · 0 comments

Comments

@michael-a-schmidt
Copy link

michael-a-schmidt commented Oct 18, 2024

Hello - first off I'd like to thank the developer(s) for creating such a great project, really impressive!

I've recently started using rpyc and I noticed something odd with how errors are handled on the server-side in the case of a remote object being used as a context manager on the client side.

Specifically, I have an application where I'd like to use a with statement in my client-side code on a remote object that has __enter__ and __exit__ functions defined. I am able to see the correct error type and message on the client side, but on the server side I get another exception:

caught an exception: type=<class 'TypeError'>, val=exceptions must derive from BaseException

I have included a minimal example below that shows the issue.

I have traced this with my debugger to rpyc.core.protocol, line 891, the function _handle_ctxexit. When my example server-side code raises a built-in error (ValueError in this case) I can see that the right exception is making its way into this function, but somehow the raise exc statement is failing, which is caught in the except Exception statement and a TypeError is returned:

    def _handle_ctxexit(self, obj, exc):  # request handler
        if exc:
            try:
                raise exc
            except Exception:
                exc, typ, tb = sys.exc_info()
        else:
            typ = tb = None
        return self._handle_getattr(obj, "__exit__")(exc, typ, tb)

My expectation is that both the client and the server should see the same type of exception, and that we should be able to filter and handle exceptions in the __exit__ statement on the remote object.

To reproduce this error, start the server, then run the client.

Server Output:

% python minimal_server.py 
in __enter__
in __exit__
caught an exception: type=<class 'TypeError'>, val=exceptions must derive from BaseException

Client output

% python minimal_client.py 
Traceback (most recent call last):
  File "/path/to/rpyc-example/minimal_client.py", line 6, in <module>
    conn.root.raise_remote()
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/netref.py", line 239, in __call__
    return syncreq(_self, consts.HANDLE_CALL, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/netref.py", line 63, in syncreq
    return conn.sync_request(handler, proxy, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/protocol.py", line 744, in sync_request
    return _async_res.value
           ^^^^^^^^^^^^^^^^
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/async_.py", line 111, in value
    raise self._obj
_get_exception_class.<locals>.Derived: Hello, World!

========= Remote Traceback (1) =========
Traceback (most recent call last):
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/protocol.py", line 369, in _dispatch_request
    res = self._HANDLERS[handler](self, *args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/rpyc-example/venv/lib/python3.11/site-packages/rpyc/core/protocol.py", line 863, in _handle_call
    return obj(*args, **dict(kwargs))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/rpyc-example/minimal_server.py", line 14, in raise_remote
    raise ValueError("Hello, World!")
ValueError: Hello, World!
Environment
  • rpyc version: 6.0.1
  • python version: 3.11.3
  • operating system: Darwin (Mac Ventura 13.6.9)
Minimal example

Server:

from rpyc import Service, ThreadedServer


class MyService(Service):
    """ example service showing TypeError in __exit__ """

    def on_connect(self, conn):
        pass

    def on_disconnect(self, conn):
        pass

    def raise_remote(self):
        raise ValueError("Hello, World!")

    def __enter__(self):
        print("in __enter__")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("in __exit__")
        if exc_type:
            print(f"caught an exception: type={exc_type}, val={exc_val}")
        return


if __name__ == "__main__":
    t = ThreadedServer(
        MyService(),
        port=18861,
        protocol_config={
            'allow_public_attrs': True
        }
    )
    t.start()

Client:

import rpyc

conn = rpyc.connect("localhost", port=18861)

with conn.root:
    conn.root.raise_remote()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant