Fixup exception handling for capabilities
This commit is contained in:
@@ -13,9 +13,27 @@ extern "C" {
|
|||||||
PyObject * wrapPyFunc(PyObject * func, PyObject * arg) {
|
PyObject * wrapPyFunc(PyObject * func, PyObject * arg) {
|
||||||
PyObject * result = PyObject_CallFunctionObjArgs(func, arg, NULL);
|
PyObject * result = PyObject_CallFunctionObjArgs(func, arg, NULL);
|
||||||
Py_DECREF(func);
|
Py_DECREF(func);
|
||||||
|
|
||||||
|
PyObject * err = PyErr_Occurred();
|
||||||
|
if(err) {
|
||||||
|
char * errorMsg = PyString_AsString(PyObject_Repr(err));
|
||||||
|
// PyErr_Clear();
|
||||||
|
throw std::invalid_argument(errorMsg);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void wrapRemoteCall(PyObject * func, capnp::Response<capnp::DynamicStruct> & arg) {
|
||||||
|
wrap_remote_call(func, arg);
|
||||||
|
|
||||||
|
PyObject * err = PyErr_Occurred();
|
||||||
|
if(err) {
|
||||||
|
char * errorMsg = PyString_AsString(PyObject_Repr(err));
|
||||||
|
// PyErr_Clear();
|
||||||
|
throw std::invalid_argument(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::kj::Promise<PyObject *> evalLater(kj::EventLoop & loop, PyObject * func) {
|
::kj::Promise<PyObject *> evalLater(kj::EventLoop & loop, PyObject * func) {
|
||||||
return loop.evalLater([func]() { return wrapPyFunc(func, NULL); } );
|
return loop.evalLater([func]() { return wrapPyFunc(func, NULL); } );
|
||||||
}
|
}
|
||||||
@@ -38,9 +56,9 @@ PyObject * wrapPyFunc(PyObject * func, PyObject * arg) {
|
|||||||
|
|
||||||
::kj::Promise<void> then(::capnp::RemotePromise< ::capnp::DynamicStruct> & promise, PyObject * func, PyObject * error_func) {
|
::kj::Promise<void> then(::capnp::RemotePromise< ::capnp::DynamicStruct> & promise, PyObject * func, PyObject * error_func) {
|
||||||
if(error_func == Py_None)
|
if(error_func == Py_None)
|
||||||
return promise.then([func](capnp::Response<capnp::DynamicStruct>&& arg) { wrap_remote_call(func, arg); } );
|
return promise.then([func](capnp::Response<capnp::DynamicStruct>&& arg) { wrapRemoteCall(func, arg); } );
|
||||||
else
|
else
|
||||||
return promise.then([func](capnp::Response<capnp::DynamicStruct>&& arg) { wrap_remote_call(func, arg); }
|
return promise.then([func](capnp::Response<capnp::DynamicStruct>&& arg) { wrapRemoteCall(func, arg); }
|
||||||
, [error_func](kj::Exception arg) { wrapPyFunc(error_func, wrap_kj_exception(arg)); } );
|
, [error_func](kj::Exception arg) { wrapPyFunc(error_func, wrap_kj_exception(arg)); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +78,19 @@ public:
|
|||||||
kj::Promise<void> call(capnp::InterfaceSchema::Method method,
|
kj::Promise<void> call(capnp::InterfaceSchema::Method method,
|
||||||
capnp::CallContext< capnp::DynamicStruct, capnp::DynamicStruct> context) {
|
capnp::CallContext< capnp::DynamicStruct, capnp::DynamicStruct> context) {
|
||||||
auto methodName = method.getProto().getName();
|
auto methodName = method.getProto().getName();
|
||||||
|
|
||||||
kj::Promise<void> * promise = call_server_method(py_server, const_cast<char *>(methodName.cStr()), context);
|
kj::Promise<void> * promise = call_server_method(py_server, const_cast<char *>(methodName.cStr()), context);
|
||||||
|
|
||||||
|
PyObject * err = PyErr_Occurred();
|
||||||
|
if(err) {
|
||||||
|
PyObject *ptype, *pvalue, *ptraceback;
|
||||||
|
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||||
|
|
||||||
|
char * errorMsg = PyString_AsString(pvalue);
|
||||||
|
PyErr_Clear();
|
||||||
|
throw std::invalid_argument(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
if(promise == nullptr)
|
if(promise == nullptr)
|
||||||
return kj::READY_NOW;
|
return kj::READY_NOW;
|
||||||
|
|
||||||
|
|||||||
@@ -39,30 +39,37 @@ import os as _os
|
|||||||
import sys as _sys
|
import sys as _sys
|
||||||
import imp as _imp
|
import imp as _imp
|
||||||
from functools import partial as _partial
|
from functools import partial as _partial
|
||||||
|
import warnings as _warnings
|
||||||
|
import inspect as _inspect
|
||||||
|
|
||||||
# By making it public, we'll be able to call it from capabilityHelper.h
|
# By making it public, we'll be able to call it from capabilityHelper.h
|
||||||
cdef public object wrap_dynamic_struct_reader(C_DynamicStruct.Reader & reader):
|
cdef public object wrap_dynamic_struct_reader(C_DynamicStruct.Reader & reader):
|
||||||
return _DynamicStructReader()._init(reader, None)
|
return _DynamicStructReader()._init(reader, None)
|
||||||
|
|
||||||
cdef public void wrap_remote_call(PyObject * func, Response & r):
|
cdef public void wrap_remote_call(PyObject * func, Response & r) except *:
|
||||||
response = _Response()._init_childptr(new Response(moveResponse(r)), None)
|
response = _Response()._init_childptr(new Response(moveResponse(r)), None)
|
||||||
|
|
||||||
func_obj = <object>func
|
func_obj = <object>func
|
||||||
# TODO: decref func?
|
# TODO: decref func?
|
||||||
func_obj(response)
|
func_obj(response)
|
||||||
|
|
||||||
cdef public VoidPromise * call_server_method(PyObject * _server, char * _method_name, CallContext & _context):
|
cdef public VoidPromise * call_server_method(PyObject * _server, char * _method_name, CallContext & _context) except *:
|
||||||
server = <object>_server
|
server = <object>_server
|
||||||
method_name = <object>_method_name
|
method_name = <object>_method_name
|
||||||
|
|
||||||
context = _CallContext()._init(_context)
|
context = _CallContext()._init(_context)
|
||||||
ret = getattr(server, method_name)(context)
|
func = getattr(server, method_name)
|
||||||
|
ret = func(context)
|
||||||
|
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
if type(ret) is _VoidPromise:
|
if type(ret) is _VoidPromise:
|
||||||
return new VoidPromise(moveVoidPromise(deref((<_VoidPromise>ret).thisptr)))
|
return new VoidPromise(moveVoidPromise(deref((<_VoidPromise>ret).thisptr)))
|
||||||
else:
|
else:
|
||||||
raise ValueError('Server function returned a value that was not a VoidPromise: ' + str(ret))
|
try:
|
||||||
|
warning_msg = 'Server function (%s) returned a value that was not a VoidPromise: return = %s' % (method_name, str(ret))
|
||||||
|
except:
|
||||||
|
warning_msg = 'Server function (%s) returned a value that was not a VoidPromise' % (method_name)
|
||||||
|
_warnings.warn_explicit(warning_msg, UserWarning, _inspect.getsourcefile(func), _inspect.getsourcelines(func)[1])
|
||||||
|
|
||||||
return NULL
|
return NULL
|
||||||
|
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ interface TestInterface {
|
|||||||
# baz @2 (s: TestAllTypes);
|
# baz @2 (s: TestAllTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
# interface TestExtends extends(TestInterface) {
|
interface TestExtends extends(TestInterface) {
|
||||||
# qux @0 ();
|
qux @0 ();
|
||||||
# corge @1 TestAllTypes -> ();
|
# corge @1 TestAllTypes -> ();
|
||||||
# grault @2 () -> TestAllTypes;
|
# grault @2 () -> TestAllTypes;
|
||||||
# }
|
}
|
||||||
|
|
||||||
# interface TestPipeline {
|
interface TestPipeline {
|
||||||
# getCap @0 (n: UInt32, inCap :TestInterface) -> (s: Text, outBox :Box);
|
getCap @0 (n: UInt32, inCap :TestInterface) -> (s: Text, outBox :Box);
|
||||||
# testPointers @1 (cap :TestInterface, obj :Object, list :List(TestInterface)) -> ();
|
testPointers @1 (cap :TestInterface, obj :Object, list :List(TestInterface)) -> ();
|
||||||
|
|
||||||
# struct Box {
|
struct Box {
|
||||||
# cap @0 :TestInterface;
|
cap @0 :TestInterface;
|
||||||
# }
|
}
|
||||||
# }
|
}
|
||||||
|
|||||||
@@ -98,3 +98,60 @@ def test_pipeline(capability):
|
|||||||
|
|
||||||
response = loop.wait_remote(remote)
|
response = loop.wait_remote(remote)
|
||||||
assert response.s == '26_foo'
|
assert response.s == '26_foo'
|
||||||
|
|
||||||
|
class BadServer:
|
||||||
|
def __init__(self, val=1):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
def foo(self, context):
|
||||||
|
context.results.x = str(context.params.i * 5 + self.val)
|
||||||
|
context.results.x2 = 5 # raises exception
|
||||||
|
|
||||||
|
def test_exception_client(capability):
|
||||||
|
loop = capnp.EventLoop()
|
||||||
|
|
||||||
|
client = capability.TestInterface.new_client(BadServer(), loop)
|
||||||
|
|
||||||
|
remote = client._send('foo', i=5)
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
loop.wait_remote(remote)
|
||||||
|
|
||||||
|
class BadPipelineServer:
|
||||||
|
def getCap(self, context):
|
||||||
|
def _then(response):
|
||||||
|
context.results.s = response.x + '_foo'
|
||||||
|
context.results.outBox.cap = capability().TestInterface.new_server(Server(100))
|
||||||
|
def _error(error):
|
||||||
|
raise Exception('test')
|
||||||
|
|
||||||
|
return context.params.inCap.foo(i=context.params.n).then(_then, _error)
|
||||||
|
|
||||||
|
def test_exception_chain(capability):
|
||||||
|
loop = capnp.EventLoop()
|
||||||
|
|
||||||
|
client = capability.TestPipeline.new_client(BadPipelineServer(), loop)
|
||||||
|
foo_client = capability.TestInterface.new_client(BadServer(), loop)
|
||||||
|
|
||||||
|
remote = client.getCap(n=5, inCap=foo_client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
loop.wait_remote(remote)
|
||||||
|
except Exception as e:
|
||||||
|
assert e.message == 'test'
|
||||||
|
|
||||||
|
def test_pipeline_exception(capability):
|
||||||
|
loop = capnp.EventLoop()
|
||||||
|
|
||||||
|
client = capability.TestPipeline.new_client(BadPipelineServer(), loop)
|
||||||
|
foo_client = capability.TestInterface.new_client(BadServer(), loop)
|
||||||
|
|
||||||
|
remote = client.getCap(n=5, inCap=foo_client)
|
||||||
|
|
||||||
|
outCap = remote.outBox.cap
|
||||||
|
pipelinePromise = outCap.foo(i=10)
|
||||||
|
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
loop.wait_remote(pipelinePromise)
|
||||||
|
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
loop.wait_remote(remote)
|
||||||
|
|||||||
Reference in New Issue
Block a user