Index: Stackless/unittests/test_channel.py =================================================================== --- Stackless/unittests/test_channel.py (revision 76627) +++ Stackless/unittests/test_channel.py (working copy) @@ -105,7 +105,39 @@ c = stackless.channel() self.failUnlessRaises(RuntimeError, c.receive) + def testSendException(self): + ''' Test that an exception sent across a channel will be received as a raised exception. ''' + # Function to send an exception. + def f(testChannel, exceptionToSend): + try: + raise exceptionToSend + except Exception as e: + testChannel.send_exception(e) + + class ExpectedError(Exception): + pass + + # Get the tasklet blocked on the channel. + channel = stackless.channel() + tasklet = stackless.tasklet(f)(channel, ExpectedError()) + tasklet.run() + + try: + # This will raise an exception + channel.receive() + self.fail("Receive should throw an exception") + except ExpectedError as e: + # The exception should have a traceback indicating its source + import traceback + tb = traceback.extract_tb(e.__traceback__) + self.assertEqual(len(tb), 2, "Traceback should contain full stack information") + self.assertEqual(tb[-1][2], 'f', "Traceback should be preserved for an exception sent across a channel") + else: + self.fail("Should have received an exeption of type ExpectedError") + + tasklet.kill() + if __name__ == '__main__': import sys if not sys.argv[1:]: Index: Stackless/module/taskletobject.c =================================================================== --- Stackless/module/taskletobject.c (revision 76627) +++ Stackless/module/taskletobject.c (working copy) @@ -855,7 +855,7 @@ static char tasklet_raise_exception__doc__[] = "tasklet.raise_exception(exc, value) -- raise an exception for the tasklet.\n\ -exc must be a subclass of Exception.\n\ +exc must be a type or instance of a type subclassing from Exception.\n\ The tasklet is immediately activated."; static PyObject * @@ -906,12 +906,44 @@ { STACKLESS_GETARG(); PyObject *result = NULL; - PyObject *klass = PySequence_GetItem(args, 0); + PyObject *klass = NULL; + PyObject *klass_or_instance = PySequence_GetItem(args, 0); - if (klass == NULL) - VALUE_ERROR("tasklet.raise_exception(e, v...)", NULL); - args = PySequence_GetSlice(args, 1, PySequence_Size(args)); - if (!args) goto err_exit; + if (klass_or_instance == NULL) + VALUE_ERROR("tasklet.raise_exception(e, [v...])", NULL); + + /* Normalize arguments to impl_tasklet_raise_exception */ + if (PyExceptionClass_Check(klass_or_instance)) + { + /* If args[0] is an exception class, the rest of the arguments + are treated as initialization parameters. */ + klass = klass_or_instance; + args = PySequence_GetSlice(args, 1, PySequence_Size(args)); + if (!args) goto err_exit; + } + else if (PyExceptionInstance_Check(klass_or_instance)) + { + /* If args[0] is an exception instance, klass is extracted + from the instance. */ + if (PySequence_Length(args) != 1) + { + Py_DECREF(klass_or_instance); + VALUE_ERROR("channel.send_exception(e, [v...])\n" + "If e is an exception instance, no other " + "arguments may be provided.", NULL); + } + klass = PyExceptionInstance_Class(klass_or_instance); + Py_INCREF(klass); + args = klass_or_instance; + } + else + { + Py_DECREF(klass_or_instance); + VALUE_ERROR("tasklet.raise_exception(e, [v...])\n" + "e must be a type or instance of a " + "type subclassing from Exception.", NULL); + } + STACKLESS_PROMOTE_ALL(); result = impl_tasklet_raise_exception( (PyTaskletObject*)myself, klass, args); Index: Stackless/module/channelobject.c =================================================================== --- Stackless/module/channelobject.c (revision 76627) +++ Stackless/module/channelobject.c (working copy) @@ -499,7 +499,7 @@ static char channel_send_exception__doc__[] = "channel.send_exception(exc, value) -- send an exception over the channel.\n\ -exc must be a subclass of Exception.\n\ +exc must be a type or instance of a type subclassing from Exception.\n\ Behavior is like channel.send, but that the receiver gets an exception."; static PyObject * @@ -547,14 +547,46 @@ { STACKLESS_GETARG(); PyObject *retval = NULL; - PyObject *klass = PySequence_GetItem(args, 0); + PyObject *klass = NULL; + PyObject *klass_or_instance = PySequence_GetItem(args, 0); - if (klass == NULL) - VALUE_ERROR("channel.send_exception(e, v...)", NULL); - args = PySequence_GetSlice(args, 1, PySequence_Size(args)); - if (!args) { - goto err_exit; + if (klass_or_instance == NULL) + VALUE_ERROR("channel.send_exception(e, [v...])", NULL); + + /* Normalize arguments to impl_channel_send_exception */ + if (PyExceptionClass_Check(klass_or_instance)) + { + /* If args[0] is an exception class, the rest of the arguments + are treated as initialization parameters. */ + klass = klass_or_instance; + args = PySequence_GetSlice(args, 1, PySequence_Size(args)); + if (!args) { + goto err_exit; + } } + else if (PyExceptionInstance_Check(klass_or_instance)) + { + /* If args[0] is an exception instance, klass is extracted + from the instance. */ + if (PySequence_Length(args) != 1) + { + Py_DECREF(klass_or_instance); + VALUE_ERROR("channel.send_exception(e, [v...])\n" + "If e is an exception instance, no other " + "arguments may be provided.", NULL); + } + klass = PyExceptionInstance_Class(klass_or_instance); + Py_INCREF(klass); + args = klass_or_instance; + } + else + { + Py_DECREF(klass_or_instance); + VALUE_ERROR("channel.send_exception(e, [v...])\n" + "e must be a type or instance of a " + "type subclassing from Exception.", NULL); + } + STACKLESS_PROMOTE_ALL(); retval = impl_channel_send_exception((PyChannelObject*)myself, klass, args); Index: Stackless/module/scheduling.c =================================================================== --- Stackless/module/scheduling.c (revision 76627) +++ Stackless/module/scheduling.c (working copy) @@ -123,12 +123,22 @@ return NULL; Py_INCREF(klass); bomb->curexc_type = klass; - tup = Py_BuildValue(PyTuple_Check(args) ? "O" : "(O)", args); - bomb->curexc_value = tup; - if (tup == NULL) { - Py_DECREF(bomb); - return NULL; + + if (PyExceptionInstance_Check(args)) + { + Py_INCREF(args); + bomb->curexc_value = args; + bomb->curexc_traceback = PyException_GetTraceback(args); } + else + { + tup = Py_BuildValue(PyTuple_Check(args) ? "O" : "(O)", args); + bomb->curexc_value = tup; + if (tup == NULL) { + Py_DECREF(bomb); + return NULL; + } + } return (PyObject *) bomb; }