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 433b11693..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); } /* @@ -226,6 +235,29 @@ fiber_eq(mrb_state *mrb, mrb_value self) return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other)); } +static mrb_value +fiber_transfer(mrb_state *mrb, mrb_value self) +{ + struct mrb_context *c = fiber_check(mrb, self); + mrb_value* a; + int len; + + mrb_get_args(mrb, "*", &a, &len); + + 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 mrb_fiber_yield(mrb_state *mrb, int len, mrb_value *a) @@ -300,6 +332,7 @@ mrb_mruby_fiber_gem_init(mrb_state* mrb) mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE()); mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY()); + mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY()); mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE()); mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1)); diff --git a/mrbgems/mruby-fiber/test/fiber.rb b/mrbgems/mruby-fiber/test/fiber.rb index e10878bf4..c2bae2259 100644 --- a/mrbgems/mruby-fiber/test/fiber.rb +++ b/mrbgems/mruby-fiber/test/fiber.rb @@ -8,6 +8,27 @@ assert('Fiber#resume') { f.resume(2) } +assert('Fiber#transfer') do + f2 = nil + f1 = Fiber.new do |v| + 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_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? +end + assert('Fiber#alive?') { f = Fiber.new{ Fiber.yield } f.resume @@ -114,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