Commit e854a0e8 authored by take_cheeze's avatar take_cheeze

make Fiber#transfer compatible with CRuby

parent d4d4f1b0
...@@ -72,6 +72,7 @@ enum mrb_fiber_state { ...@@ -72,6 +72,7 @@ enum mrb_fiber_state {
MRB_FIBER_RUNNING, MRB_FIBER_RUNNING,
MRB_FIBER_RESUMING, MRB_FIBER_RESUMING,
MRB_FIBER_SUSPENDED, MRB_FIBER_SUSPENDED,
MRB_FIBER_TRANSFERRED,
MRB_FIBER_TERMINATED, MRB_FIBER_TERMINATED,
}; };
......
...@@ -136,27 +136,10 @@ fiber_result(mrb_state *mrb, mrb_value *a, int len) ...@@ -136,27 +136,10 @@ fiber_result(mrb_state *mrb, mrb_value *a, int len)
/* mark return from context modifying method */ /* mark return from context modifying method */
#define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
/*
* call-seq:
* fiber.resume(args, ...) -> obj
*
* Resumes the fiber from the point at which the last <code>Fiber.yield</code>
* was called, or starts running it if it is the first call to
* <code>resume</code>. Arguments passed to resume will be the value of
* the <code>Fiber.yield</code> expression or will be passed as block
* parameters to the fiber's block if this is the first <code>resume</code>.
*
* Alternatively, when resume is called it evaluates to the arguments passed
* to the next <code>Fiber.yield</code> statement inside the fiber's block
* or to the block value if it runs to completion without any
* <code>Fiber.yield</code>
*/
static mrb_value static mrb_value
fiber_resume(mrb_state *mrb, mrb_value self) 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); struct mrb_context *c = fiber_check(mrb, self);
mrb_value *a;
int len;
mrb_callinfo *ci; mrb_callinfo *ci;
for (ci = c->ci; ci >= c->cibase; ci--) { for (ci = c->ci; ci >= c->cibase; ci--) {
...@@ -164,6 +147,9 @@ fiber_resume(mrb_state *mrb, mrb_value self) ...@@ -164,6 +147,9 @@ fiber_resume(mrb_state *mrb, mrb_value self)
mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary"); 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) { if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) {
mrb_raise(mrb, E_FIBER_ERROR, "double resume"); mrb_raise(mrb, E_FIBER_ERROR, "double resume");
} }
...@@ -171,7 +157,8 @@ fiber_resume(mrb_state *mrb, mrb_value self) ...@@ -171,7 +157,8 @@ fiber_resume(mrb_state *mrb, mrb_value self)
mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber"); mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
} }
mrb_get_args(mrb, "*", &a, &len); mrb_get_args(mrb, "*", &a, &len);
mrb->c->status = MRB_FIBER_RESUMING; 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) { if (c->status == MRB_FIBER_CREATED) {
mrb_value *b = c->stack+1; mrb_value *b = c->stack+1;
mrb_value *e = b + len; mrb_value *e = b + len;
...@@ -180,7 +167,6 @@ fiber_resume(mrb_state *mrb, mrb_value self) ...@@ -180,7 +167,6 @@ fiber_resume(mrb_state *mrb, mrb_value self)
*b++ = *a++; *b++ = *a++;
} }
c->cibase->argc = len; c->cibase->argc = len;
c->prev = mrb->c;
if (c->prev->fib) if (c->prev->fib)
mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)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); mrb_write_barrier(mrb, (struct RBasic*)c->fib);
...@@ -191,7 +177,6 @@ fiber_resume(mrb_state *mrb, mrb_value self) ...@@ -191,7 +177,6 @@ fiber_resume(mrb_state *mrb, mrb_value self)
return c->ci->proc->env->stack[0]; return c->ci->proc->env->stack[0];
} }
MARK_CONTEXT_MODIFY(c); MARK_CONTEXT_MODIFY(c);
c->prev = mrb->c;
if (c->prev->fib) if (c->prev->fib)
mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)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); mrb_write_barrier(mrb, (struct RBasic*)c->fib);
...@@ -200,6 +185,30 @@ fiber_resume(mrb_state *mrb, mrb_value self) ...@@ -200,6 +185,30 @@ fiber_resume(mrb_state *mrb, mrb_value self)
return fiber_result(mrb, a, len); return fiber_result(mrb, a, len);
} }
/*
* call-seq:
* fiber.resume(args, ...) -> obj
*
* Resumes the fiber from the point at which the last <code>Fiber.yield</code>
* was called, or starts running it if it is the first call to
* <code>resume</code>. Arguments passed to resume will be the value of
* the <code>Fiber.yield</code> expression or will be passed as block
* parameters to the fiber's block if this is the first <code>resume</code>.
*
* Alternatively, when resume is called it evaluates to the arguments passed
* to the next <code>Fiber.yield</code> statement inside the fiber's block
* or to the block value if it runs to completion without any
* <code>Fiber.yield</code>
*/
static mrb_value
fiber_resume(mrb_state *mrb, mrb_value self)
{
mrb_value *a;
int len;
mrb_get_args(mrb, "*", &a, &len);
return fiber_switch(mrb, self, len, a, TRUE);
}
/* /*
* call-seq: * call-seq:
* fiber.alive? -> true or false * fiber.alive? -> true or false
...@@ -229,14 +238,25 @@ fiber_eq(mrb_state *mrb, mrb_value self) ...@@ -229,14 +238,25 @@ fiber_eq(mrb_state *mrb, mrb_value self)
static mrb_value static mrb_value
fiber_transfer(mrb_state *mrb, mrb_value self) 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_get_args(mrb, "*", &a, &len);
mrb_assert(mrb->c->prev); if (c == mrb->root_c) {
mrb_assert(mrb->c->prev->prev); mrb->c->status = MRB_FIBER_TRANSFERRED;
mrb->c->prev->status = MRB_FIBER_SUSPENDED; mrb->c = c;
mrb->c->prev = mrb->c->prev->prev; 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 result; return fiber_switch(mrb, self, len, a, FALSE);
} }
mrb_value mrb_value
......
...@@ -9,22 +9,24 @@ assert('Fiber#resume') { ...@@ -9,22 +9,24 @@ assert('Fiber#resume') {
} }
assert('Fiber#transfer') do assert('Fiber#transfer') do
f2 = nil
f1 = Fiber.new do |v| f1 = Fiber.new do |v|
assert_raise(FiberError) { Fiber.current.transfer }
Fiber.yield v Fiber.yield v
f2.transfer
end end
f2 = Fiber.new do f2 = Fiber.new do
f1.transfer(1)
f1.transfer(1) f1.transfer(1)
Fiber.yield 2 Fiber.yield 2
end end
assert_equal 1, f2.resume assert_equal 1, f2.resume
assert_equal 2, f2.resume assert_raise(FiberError) { f2.resume }
f1.resume assert_equal 2, f2.transfer
assert_raise(FiberError) { f1.resume }
f1.transfer
f2.resume f2.resume
assert_false f1.alive? assert_false f1.alive?
assert_false f2.alive? assert_false f2.alive?
assert_raise(FiberError) { Fiber.current.transfer }
end end
assert('Fiber#alive?') { assert('Fiber#alive?') {
...@@ -133,3 +135,74 @@ end ...@@ -133,3 +135,74 @@ end
assert('Fiber without block') do assert('Fiber without block') do
assert_raise(ArgumentError) { Fiber.new } assert_raise(ArgumentError) { Fiber.new }
end 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment