mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
make Fiber#transfer compatible with CRuby
This commit is contained in:
@@ -72,6 +72,7 @@ enum mrb_fiber_state {
|
||||
MRB_FIBER_RUNNING,
|
||||
MRB_FIBER_RESUMING,
|
||||
MRB_FIBER_SUSPENDED,
|
||||
MRB_FIBER_TRANSFERRED,
|
||||
MRB_FIBER_TERMINATED,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 (b<e) {
|
||||
*b++ = *a++;
|
||||
}
|
||||
c->cibase->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 (b<e) {
|
||||
*b++ = *a++;
|
||||
}
|
||||
c->cibase->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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user