diff --git a/include/mruby.h b/include/mruby.h index 3e0e796d2..5de47ae84 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -72,6 +72,7 @@ enum mrb_fiber_state { MRB_FIBER_RUNNING, MRB_FIBER_RESUMING, MRB_FIBER_SUSPENDED, + MRB_FIBER_TRANSFERRED, MRB_FIBER_TERMINATED, }; diff --git a/mrbgems/mruby-fiber/src/fiber.c b/mrbgems/mruby-fiber/src/fiber.c index b5f9b69dc..b077def8b 100644 --- a/mrbgems/mruby-fiber/src/fiber.c +++ b/mrbgems/mruby-fiber/src/fiber.c @@ -136,6 +136,55 @@ fiber_result(mrb_state *mrb, mrb_value *a, int len) /* mark return from context modifying method */ #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL +static mrb_value +fiber_switch(mrb_state *mrb, mrb_value self, int len, const mrb_value *a, mrb_bool resume) +{ + struct mrb_context *c = fiber_check(mrb, self); + mrb_callinfo *ci; + + for (ci = c->ci; ci >= c->cibase; ci--) { + if (ci->acc < 0) { + mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); + } + } + if (resume && c->status == MRB_FIBER_TRANSFERRED) { + mrb_raise(mrb, E_FIBER_ERROR, "resuming transfered fiber"); + } + if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) { + mrb_raise(mrb, E_FIBER_ERROR, "double resume"); + } + if (c->status == MRB_FIBER_TERMINATED) { + mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber"); + } + mrb_get_args(mrb, "*", &a, &len); + mrb->c->status = resume ? MRB_FIBER_RESUMING : MRB_FIBER_TRANSFERRED; + c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c); + if (c->status == MRB_FIBER_CREATED) { + mrb_value *b = c->stack+1; + mrb_value *e = b + len; + + while (bcibase->argc = len; + if (c->prev->fib) + mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); + mrb_write_barrier(mrb, (struct RBasic*)c->fib); + c->status = MRB_FIBER_RUNNING; + mrb->c = c; + + MARK_CONTEXT_MODIFY(c); + return c->ci->proc->env->stack[0]; + } + MARK_CONTEXT_MODIFY(c); + if (c->prev->fib) + mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); + mrb_write_barrier(mrb, (struct RBasic*)c->fib); + c->status = MRB_FIBER_RUNNING; + mrb->c = c; + return fiber_result(mrb, a, len); +} + /* * call-seq: * fiber.resume(args, ...) -> obj @@ -154,50 +203,10 @@ fiber_result(mrb_state *mrb, mrb_value *a, int len) static mrb_value fiber_resume(mrb_state *mrb, mrb_value self) { - struct mrb_context *c = fiber_check(mrb, self); mrb_value *a; int len; - mrb_callinfo *ci; - - for (ci = c->ci; ci >= c->cibase; ci--) { - if (ci->acc < 0) { - mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); - } - } - if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) { - mrb_raise(mrb, E_FIBER_ERROR, "double resume"); - } - if (c->status == MRB_FIBER_TERMINATED) { - mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber"); - } mrb_get_args(mrb, "*", &a, &len); - mrb->c->status = MRB_FIBER_RESUMING; - if (c->status == MRB_FIBER_CREATED) { - mrb_value *b = c->stack+1; - mrb_value *e = b + len; - - while (bcibase->argc = len; - c->prev = mrb->c; - if (c->prev->fib) - mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); - mrb_write_barrier(mrb, (struct RBasic*)c->fib); - c->status = MRB_FIBER_RUNNING; - mrb->c = c; - - MARK_CONTEXT_MODIFY(c); - return c->ci->proc->env->stack[0]; - } - MARK_CONTEXT_MODIFY(c); - c->prev = mrb->c; - if (c->prev->fib) - mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib); - mrb_write_barrier(mrb, (struct RBasic*)c->fib); - c->status = MRB_FIBER_RUNNING; - mrb->c = c; - return fiber_result(mrb, a, len); + return fiber_switch(mrb, self, len, a, TRUE); } /* @@ -229,14 +238,25 @@ fiber_eq(mrb_state *mrb, mrb_value self) static mrb_value fiber_transfer(mrb_state *mrb, mrb_value self) { - mrb_value result = fiber_resume(mrb, self); + struct mrb_context *c = fiber_check(mrb, self); + mrb_value* a; + int len; - mrb_assert(mrb->c->prev); - mrb_assert(mrb->c->prev->prev); - mrb->c->prev->status = MRB_FIBER_SUSPENDED; - mrb->c->prev = mrb->c->prev->prev; + mrb_get_args(mrb, "*", &a, &len); - return result; + if (c == mrb->root_c) { + mrb->c->status = MRB_FIBER_TRANSFERRED; + mrb->c = c; + c->status = MRB_FIBER_RUNNING; + MARK_CONTEXT_MODIFY(c); + return fiber_result(mrb, a, len); + } + + if (c == mrb->c) { + return fiber_result(mrb, a, len); + } + + return fiber_switch(mrb, self, len, a, FALSE); } mrb_value diff --git a/mrbgems/mruby-fiber/test/fiber.rb b/mrbgems/mruby-fiber/test/fiber.rb index c6f9d821f..c2bae2259 100644 --- a/mrbgems/mruby-fiber/test/fiber.rb +++ b/mrbgems/mruby-fiber/test/fiber.rb @@ -9,22 +9,24 @@ assert('Fiber#resume') { } assert('Fiber#transfer') do + f2 = nil f1 = Fiber.new do |v| - assert_raise(FiberError) { Fiber.current.transfer } Fiber.yield v + f2.transfer end f2 = Fiber.new do + f1.transfer(1) f1.transfer(1) Fiber.yield 2 end assert_equal 1, f2.resume - assert_equal 2, f2.resume - f1.resume + assert_raise(FiberError) { f2.resume } + assert_equal 2, f2.transfer + assert_raise(FiberError) { f1.resume } + f1.transfer f2.resume assert_false f1.alive? assert_false f2.alive? - - assert_raise(FiberError) { Fiber.current.transfer } end assert('Fiber#alive?') { @@ -133,3 +135,74 @@ end assert('Fiber without block') do assert_raise(ArgumentError) { Fiber.new } end + + +assert('Transfer to self.') do + result = [] + f = Fiber.new { result << :start; f.transfer; result << :end } + f.transfer + assert_equal [:start, :end], result + + result = [] + f = Fiber.new { result << :start; f.transfer; result << :end } + f.resume + assert_equal [:start, :end], result +end + +assert('Resume transferred fiber') do + f = Fiber.new { + assert_raise(FiberError) { f.resume } + } + f.transfer +end + +assert('Root fiber transfer.') do + result = nil + root = Fiber.current + f = Fiber.new { + result = :ok + root.transfer + } + f.resume + assert_true f.alive? + assert_equal :ok, result +end + +assert('Break nested fiber with root fiber transfer') do + root = Fiber.current + + result = nil + f2 = nil + f1 = Fiber.new { + Fiber.yield f2.resume + result = :f1 + } + f2 = Fiber.new { + result = :to_root + root.transfer :from_f2 + result = :f2 + } + assert_equal :from_f2, f1.resume + assert_equal :to_root, result + assert_equal :f2, f2.transfer + assert_equal :f2, result + assert_false f2.alive? + assert_equal :f1, f1.resume + assert_equal :f1, result + assert_false f1.alive? +end + +assert('CRuby Fiber#transfer test.') do + ary = [] + f2 = nil + f1 = Fiber.new{ + ary << f2.transfer(:foo) + :ok + } + f2 = Fiber.new{ + ary << f1.transfer(:baz) + :ng + } + assert_equal :ok, f1.transfer + assert_equal [:baz], ary +end