From c5e4cd063838033dff617f360a5b670b9eedaece Mon Sep 17 00:00:00 2001 From: ko1 Date: Tue, 21 Aug 2007 18:51:39 +0000 Subject: [PATCH] * cont.c: add Fiber#resume and Fiber.yield. and Fiber::Core class to realize Coroutine. * include/ruby/intern.h: declare rb_fiber_yield(), rb_fiber_resume(), * enumerator.c: use above api. * test/ruby/test_fiber.rb: fix and add tests for above changes. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@13130 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 11 ++++ cont.c | 141 ++++++++++++++++++++++++++++------------ enumerator.c | 8 +-- include/ruby/intern.h | 3 +- test/ruby/test_fiber.rb | 81 ++++++++++++++--------- version.h | 6 +- 6 files changed, 169 insertions(+), 81 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6e5be1c1ca..2c02612244 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +Wed Aug 22 03:51:07 2007 Koichi Sasada + + * cont.c: add Fiber#resume and Fiber.yield. + and Fiber::Core class to realize Coroutine. + + * include/ruby/intern.h: declare rb_fiber_yield(), rb_fiber_resume(), + + * enumerator.c: use above api. + + * test/ruby/test_fiber.rb: fix and add tests for above changes. + Tue Aug 21 21:09:48 2007 Tanaka Akira * lib/tmpdir.rb (Dir.mktmpdir): make directory suffix specifiable. diff --git a/cont.c b/cont.c index fe2216d186..f0b21a8ec3 100644 --- a/cont.c +++ b/cont.c @@ -18,7 +18,6 @@ typedef struct rb_context_struct { VALUE self; VALUE value; - VALUE prev; /* for fiber */ VALUE *vm_stack; VALUE *machine_stack; VALUE *machine_stack_src; @@ -30,11 +29,14 @@ typedef struct rb_context_struct { rb_thread_t saved_thread; rb_jmpbuf_t jmpbuf; int machine_stack_size; + /* for cont */ + VALUE prev; int alive; } rb_context_t; VALUE rb_cCont; VALUE rb_cFiber; +VALUE rb_cFiberCore; VALUE rb_eFiberError; #define GetContPtr(obj, ptr) \ @@ -52,7 +54,6 @@ cont_mark(void *ptr) rb_context_t *cont = ptr; rb_gc_mark(cont->value); rb_gc_mark(cont->prev); - rb_thread_mark(&cont->saved_thread); if (cont->vm_stack) { @@ -511,20 +512,38 @@ rb_fiber_s_new(VALUE self) return contval; } +static VALUE +return_fiber(void) +{ + rb_context_t *cont; + VALUE curr = rb_fiber_current(); + GetContPtr(curr, cont); + + if (cont->prev == Qnil) { + rb_thread_t *th = GET_THREAD(); + + if (th->root_fiber != curr) { + return th->root_fiber; + } + else { + rb_raise(rb_eFiberError, "can't yield from root fiber"); + } + } + else { + VALUE prev = cont->prev; + cont->prev = Qnil; + return prev; + } +} + +VALUE rb_fiber_transfer(VALUE fib, int argc, VALUE *argv); + static void rb_fiber_terminate(rb_context_t *cont) { - rb_context_t *prev_cont; VALUE value = cont->value; - - GetContPtr(cont->prev, prev_cont); cont->alive = Qfalse; - if (prev_cont->alive == Qfalse) { - rb_fiber_yield(GET_THREAD()->root_fiber, 1, &value); - } - else { - rb_fiber_yield(cont->prev, 1, &value); - } + rb_fiber_transfer(return_fiber(), 1, &value); } void @@ -551,7 +570,13 @@ rb_fiber_start(void) TH_POP_TAG(); if (state) { - th->thrown_errinfo = vm_make_jump_tag_but_local_jump(state, th->errinfo); + if (TAG_RAISE) { + th->thrown_errinfo = th->errinfo; + } + else { + th->thrown_errinfo = + vm_make_jump_tag_but_local_jump(state, th->errinfo); + } th->interrupt_flag = 1; } @@ -565,7 +590,9 @@ rb_fiber_current() rb_thread_t *th = GET_THREAD(); if (th->fiber == 0) { /* save root */ - th->root_fiber = th->fiber = cont_new(rb_cFiber)->self; + rb_context_t *cont = cont_new(rb_cFiberCore); + cont->prev = Qnil; + th->root_fiber = th->fiber = cont->self; } return th->fiber; } @@ -583,12 +610,10 @@ cont_store(rb_context_t *next_cont) else { /* create current fiber */ cont = cont_new(rb_cFiber); /* no need to allocate vm stack */ + cont->prev = Qnil; th->root_fiber = th->fiber = cont->self; } - if (cont->alive) { - next_cont->prev = cont->self; - } cont_save_machine_stack(th, cont); if (ruby_setjmp(cont->jmpbuf)) { @@ -601,8 +626,8 @@ cont_store(rb_context_t *next_cont) } } -VALUE -rb_fiber_yield(VALUE fib, int argc, VALUE *argv) +static inline VALUE +fiber_switch(VALUE fib, int argc, VALUE *argv, int is_resume) { VALUE value; rb_context_t *cont; @@ -613,35 +638,53 @@ rb_fiber_yield(VALUE fib, int argc, VALUE *argv) if (cont->saved_thread.self != th->self) { rb_raise(rb_eFiberError, "fiber called across threads"); } - if (cont->saved_thread.trap_tag != th->trap_tag) { + else if (cont->saved_thread.trap_tag != th->trap_tag) { rb_raise(rb_eFiberError, "fiber called across trap"); } - if (!cont->alive) { + else if (!cont->alive) { rb_raise(rb_eFiberError, "dead fiber called"); } + if (is_resume) { + cont->prev = rb_fiber_current(); + } cont->value = make_passing_arg(argc, argv); if ((value = cont_store(cont)) == Qundef) { cont_restore_0(cont, (VALUE *)&cont); - rb_bug("rb_fiber_yield: unreachable"); + rb_bug("rb_fiber_resume: unreachable"); } - + + RUBY_VM_CHECK_INTS(); + return value; } -static VALUE -rb_fiber_m_yield(int argc, VALUE *argv, VALUE fib) +VALUE +rb_fiber_transfer(VALUE fib, int argc, VALUE *argv) { - return rb_fiber_yield(fib, argc, argv); + return fiber_switch(fib, argc, argv, 0); } -static VALUE -rb_fiber_prev(VALUE fib) +VALUE +rb_fiber_resume(VALUE fib, int argc, VALUE *argv) { + int i; rb_context_t *cont; + VALUE curr = rb_fiber_current(); GetContPtr(fib, cont); - return cont->prev; + + if (cont->prev != Qnil) { + rb_raise(rb_eFiberError, "double resume"); + } + + return fiber_switch(fib, argc, argv, 1); +} + +VALUE +rb_fiber_yield(int argc, VALUE *argv) +{ + rb_fiber_transfer(return_fiber(), argc, argv); } VALUE @@ -652,24 +695,30 @@ rb_fiber_alive_p(VALUE fib) return cont->alive; } +static VALUE +rb_fiber_m_resume(int argc, VALUE *argv, VALUE fib) +{ + return rb_fiber_resume(fib, argc, argv); +} + +static VALUE +rb_fiber_m_transfer(int argc, VALUE *argv, VALUE fib) +{ + return rb_fiber_transfer(fib, argc, argv); +} + +static VALUE +rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass) +{ + return rb_fiber_yield(argc, argv); +} + static VALUE rb_fiber_s_current(VALUE klass) { return rb_fiber_current(); } -static VALUE -rb_fiber_s_prev(VALUE klass) -{ - return rb_fiber_prev(rb_fiber_s_current(Qnil)); -} - -static VALUE -rb_fiber_s_yield(int argc, VALUE *argv, VALUE fib) -{ - return rb_fiber_yield(rb_fiber_s_prev(Qnil), argc, argv); -} - void Init_Cont(void) { @@ -682,15 +731,21 @@ Init_Cont(void) rb_cFiber = rb_define_class("Fiber", rb_cObject); rb_undef_alloc_func(rb_cFiber); - rb_define_method(rb_cFiber, "yield", rb_fiber_m_yield, -1); - rb_define_method(rb_cFiber, "prev", rb_fiber_prev, 0); + rb_define_method(rb_cFiber, "resume", rb_fiber_m_resume, -1); rb_define_method(rb_cFiber, "alive?", rb_fiber_alive_p, 0); rb_define_singleton_method(rb_cFiber, "current", rb_fiber_s_current, 0); - rb_define_singleton_method(rb_cFiber, "prev", rb_fiber_s_prev, 0); rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1); rb_define_singleton_method(rb_cFiber, "new", rb_fiber_s_new, 0); + rb_cFiberCore = rb_define_class_under(rb_cFiber, "Core", rb_cObject); + rb_undef_alloc_func(rb_cFiberCore); + rb_define_method(rb_cFiberCore, "transfer", rb_fiber_m_transfer, -1); + rb_define_method(rb_cFiberCore, "alive?", rb_fiber_alive_p, 0); + + rb_define_singleton_method(rb_cFiberCore, "current", rb_fiber_s_current, 0); + rb_define_singleton_method(rb_cFiberCore, "new", rb_fiber_s_new, 0); + rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); } diff --git a/enumerator.c b/enumerator.c index e2be0e2e85..2557808acb 100644 --- a/enumerator.c +++ b/enumerator.c @@ -373,7 +373,7 @@ next_ii(VALUE i, VALUE obj) VALUE tmp = e->next; e->next = i; - tmp = rb_fiber_yield(e->dst, 1, &tmp); + tmp = rb_fiber_yield(1, &tmp); if (tmp != Qnil) { e->dst = tmp; } @@ -388,7 +388,7 @@ next_i(VALUE curr, VALUE obj) rb_block_call(obj, rb_intern("each"), 0, 0, next_ii, obj); e->has_next = Qfalse; - rb_fiber_yield(e->dst, 1, &e->next); + rb_fiber_yield(1, &e->next); } static void @@ -398,7 +398,7 @@ next_init(VALUE obj, struct enumerator *e) e->dst = curr; e->fib = rb_block_call(rb_cFiber, rb_intern("new"), 0, 0, next_i, obj); e->has_next = Qtrue; - rb_fiber_yield(e->fib, 1, &curr); + rb_fiber_resume(e->fib, 1, &curr); } /* @@ -432,7 +432,7 @@ enumerator_next(VALUE obj) rb_raise(rb_eStopIteration, "Enumerator#each reached at end"); } - v = rb_fiber_yield(e->fib, 1, &curr); + v = rb_fiber_resume(e->fib, 1, &curr); return v; } diff --git a/include/ruby/intern.h b/include/ruby/intern.h index a57bb1ed9e..485d3909b6 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -148,7 +148,8 @@ VALUE rb_singleton_class(VALUE); int rb_cmpint(VALUE, VALUE, VALUE); NORETURN(void rb_cmperr(VALUE, VALUE)); /* cont.c */ -VALUE rb_fiber_yield(VALUE fib, int argc, VALUE *args); +VALUE rb_fiber_resume(VALUE fib, int argc, VALUE *args); +VALUE rb_fiber_yield(int argc, VALUE *args); VALUE rb_fiber_current(void); /* enum.c */ /* enumerator.c */ diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index d6212cc7ea..bd39ba5af4 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -6,25 +6,24 @@ class TestFiber < Test::Unit::TestCase assert_equal(:ok2, Fiber.new{|e| assert_equal(:ok1, e) - assert_equal(f, Fiber.prev) Fiber.yield :ok2 - }.yield(:ok1) + }.resume(:ok1) ) - assert_equal([:a, :b], Fiber.new{|a, b| [a, b]}.yield(:a, :b)) + assert_equal([:a, :b], Fiber.new{|a, b| [a, b]}.resume(:a, :b)) end def test_term - assert_equal(:ok, Fiber.new{:ok}.yield) + assert_equal(:ok, Fiber.new{:ok}.resume) assert_equal([:a, :b, :c, :d, :e], Fiber.new{ Fiber.new{ Fiber.new{ Fiber.new{ [:a] - }.yield + [:b] - }.yield + [:c] - }.yield + [:d] - }.yield + [:e]) + }.resume + [:b] + }.resume + [:c] + }.resume + [:d] + }.resume + [:e]) end def test_many_fibers @@ -35,7 +34,7 @@ class TestFiber < Test::Unit::TestCase assert_equal(max, max.times{|i| Fiber.new{ - }.yield + }.resume } ) end @@ -48,7 +47,7 @@ class TestFiber < Test::Unit::TestCase max.times{|i| Fiber.new{ @cnt += 1 - }.yield + }.resume } } }.each{|t| @@ -63,50 +62,72 @@ class TestFiber < Test::Unit::TestCase } assert_raise(FiberError){ f = Fiber.new{} - Thread.new{f.yield}.join # Fiber yielding across thread + Thread.new{f.resume}.join # Fiber yielding across thread } assert_raise(FiberError){ f = Fiber.new{} - f.yield - f.yield + f.resume + f.resume } assert_raise(RuntimeError){ f = Fiber.new{ @c = callcc{|c| @c = c} - }.yield + }.resume @c.call # cross fiber callcc } - end - - def test_loop - ary = [] - f2 = nil - f1 = Fiber.new{ - ary << f2.yield(:foo) - :bar + assert_raise(RuntimeError){ + Fiber.new{ + raise + }.resume } - f2 = Fiber.new{ - ary << f1.yield(:baz) - :ok + assert_raise(FiberError){ + Fiber.yield + } + assert_raise(FiberError){ + fib = Fiber.new{ + fib.resume + } + fib.resume + } + assert_raise(FiberError){ + fib = Fiber.new{ + Fiber.new{ + fib.resume + }.resume + } + fib.resume } - assert_equal(:ok, f1.yield) - assert_equal([:baz, :bar], ary) end def test_return assert_raise(LocalJumpError){ Fiber.new do return - end.yield + end.resume } end def test_throw - assert_raise(RuntimeError){ + assert_raise(NameError){ Fiber.new do throw :a - end.yield + end.resume } end + + def test_transfer + ary = [] + f2 = nil + f1 = Fiber::Core.new{ + ary << f2.transfer(:foo) + :ok + } + f2 = Fiber::Core.new{ + ary << f1.transfer(:baz) + :ng + } + assert_equal(:ok, f1.transfer) + assert_equal([:baz], ary) + end end diff --git a/version.h b/version.h index dcbfe5dee7..9479b730e0 100644 --- a/version.h +++ b/version.h @@ -1,7 +1,7 @@ #define RUBY_VERSION "1.9.0" -#define RUBY_RELEASE_DATE "2007-08-21" +#define RUBY_RELEASE_DATE "2007-08-22" #define RUBY_VERSION_CODE 190 -#define RUBY_RELEASE_CODE 20070821 +#define RUBY_RELEASE_CODE 20070822 #define RUBY_PATCHLEVEL 0 #define RUBY_VERSION_MAJOR 1 @@ -9,7 +9,7 @@ #define RUBY_VERSION_TEENY 0 #define RUBY_RELEASE_YEAR 2007 #define RUBY_RELEASE_MONTH 8 -#define RUBY_RELEASE_DAY 21 +#define RUBY_RELEASE_DAY 22 #ifdef RUBY_EXTERN RUBY_EXTERN const char ruby_version[];