From 943ab6aeac25fb382a5e4e8c8f5ad1b6d0fb183d Mon Sep 17 00:00:00 2001 From: Knut Franke Date: Thu, 9 Oct 2014 01:03:42 +0200 Subject: [PATCH] Implement Fiber#raise --- cont.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_fiber.rb | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/cont.c b/cont.c index 2ff2cbb..b52cd96 100644 --- a/cont.c +++ b/cont.c @@ -941,6 +941,8 @@ static VALUE make_passing_arg(int argc, const VALUE *argv) { switch (argc) { + case -1: + return argv[0]; case 0: return Qnil; case 1: @@ -1559,6 +1561,49 @@ rb_fiber_m_resume(int argc, VALUE *argv, VALUE fib) /* * call-seq: + * fiber.raise(string) -> obj + * fiber.raise(exception [, string [, array]]) -> obj + * + * Assuming that resume was called before, raises an exception in + * the fiber at the point at which the last Fiber.yield was + * called. If the Fiber.yield occurs within a + * begin...end block with a +rescue+ clause matching the + * exception being raised, fiber.raise evaluates to the arguments + * passed to the next Fiber.yield statement inside the fiber's + * block or to the block value if it runs to completion without any + * Fiber.yield. Otherwise, the exception is propagated to the + * caller. + * + * If the fiber either has never been resumed or is dead, raises FiberError. + * + * With a single +String+ argument, raises a +RuntimeError+ with the string as + * a message. Otherwise, the first parameter should be the name of an + * +Exception+ class (or an object that returns an +Exception+ object when + * sent an +exception+ message). The optional second parameter sets the + * message associated with the exception, and the third parameter is an array + * of callback information. + */ +static VALUE +rb_fiber_raise(int argc, VALUE *argv, VALUE fibval) +{ + VALUE exc; + rb_fiber_t *fib; + GetFiberPtr(fibval, fib); + switch(fib->status) { + case CREATED: + rb_raise(rb_eFiberError, "Cannot raise exception in an unborn fiber." + " You need to resume first."); + case RUNNING: + exc = rb_make_exception(argc, argv); + return rb_fiber_resume(fibval, -1, &exc); + case TERMINATED: + rb_raise(rb_eFiberError, "Cannot raise exception in a dead fiber."); + } + return Qundef; +} + +/* + * call-seq: * fiber.transfer(args, ...) -> obj * * Transfer control to another fiber, resuming it from where it last @@ -1678,6 +1723,7 @@ Init_Cont(void) rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1); rb_define_method(rb_cFiber, "initialize", rb_fiber_init, 0); rb_define_method(rb_cFiber, "resume", rb_fiber_m_resume, -1); + rb_define_method(rb_cFiber, "raise", rb_fiber_raise, -1); } RUBY_SYMBOL_EXPORT_BEGIN diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index ed071ac..d8bae1f 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -107,6 +107,15 @@ class TestFiber < Test::Unit::TestCase } fib.resume } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.raise "raise in unborn fiber" + } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.resume + fib.raise "raise in dead fiber" + } end def test_return @@ -125,6 +134,37 @@ class TestFiber < Test::Unit::TestCase } end + def test_raise + assert_raise(ZeroDivisionError){ + Fiber.new do + 1/0 + end.resume + } + assert_raise(RuntimeError){ + fib = Fiber.new{ Fiber.yield } + fib.raise "raise and propagate" + } + assert_nothing_raised{ + fib = Fiber.new do + begin + Fiber.yield + rescue + end + end + fib.resume + fib.raise "rescue in fiber" + } + fib = Fiber.new do + begin + Fiber.yield + rescue + Fiber.yield :ok + end + end + fib.resume + assert_equal(:ok, fib.raise) + end + def test_transfer ary = [] f2 = nil -- 1.9.1