Created
January 26, 2009 02:56
-
-
Save tmm1/52671 to your computer and use it in GitHub Desktop.
Fiber implementation for Ruby 1.8.7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/eval.c b/eval.c | |
| index 11264f7..01d127b 100644 | |
| --- a/eval.c | |
| +++ b/eval.c | |
| @@ -1026,6 +1026,7 @@ static struct tag *prot_tag; | |
| #define PROT_LOOP INT2FIX(1) /* 3 */ | |
| #define PROT_LAMBDA INT2FIX(2) /* 5 */ | |
| #define PROT_YIELD INT2FIX(3) /* 7 */ | |
| +#define PROT_FIBER INT2FIX(4) /* 9 */ | |
| #define EXEC_TAG() ruby_setjmp(((void)0), prot_tag->buf) | |
| @@ -4856,6 +4857,9 @@ return_jump(retval) | |
| tt->retval = retval; | |
| JUMP_TAG(TAG_RETURN); | |
| } | |
| + if (tt->tag == PROT_FIBER) { | |
| + localjump_error("unexpected return", retval, TAG_RETURN); | |
| + } | |
| if (tt->tag == PROT_THREAD) { | |
| rb_raise(rb_eThreadError, "return can't jump across threads"); | |
| } | |
| @@ -4877,6 +4881,7 @@ break_jump(retval) | |
| case PROT_YIELD: | |
| case PROT_LOOP: | |
| case PROT_LAMBDA: | |
| + case PROT_FIBER: | |
| tt->dst = (VALUE)tt->frame->uniq; | |
| tt->retval = retval; | |
| JUMP_TAG(TAG_BREAK); | |
| @@ -4906,6 +4911,7 @@ next_jump(retval) | |
| case PROT_LOOP: | |
| case PROT_LAMBDA: | |
| case PROT_FUNC: | |
| + case PROT_FIBER: | |
| tt->dst = (VALUE)tt->frame->uniq; | |
| tt->retval = retval; | |
| JUMP_TAG(TAG_NEXT); | |
| @@ -5141,6 +5147,7 @@ rb_yield_0(val, self, klass, flags, avalue) | |
| tt->retval = result; | |
| JUMP_TAG(TAG_BREAK); | |
| } | |
| + if (tt->tag == PROT_THREAD || tt->tag == PROT_FIBER) break; | |
| tt = tt->prev; | |
| } | |
| proc_jump_error(TAG_BREAK, result); | |
| @@ -6572,6 +6579,7 @@ eval(self, src, scope, file, line) | |
| scope_dup(ruby_scope); | |
| for (tag=prot_tag; tag; tag=tag->prev) { | |
| + if (tag->tag == PROT_THREAD || tag->tag == PROT_FIBER) break; | |
| scope_dup(tag->scope); | |
| } | |
| for (vars = ruby_dyna_vars; vars; vars = vars->next) { | |
| @@ -10160,6 +10168,11 @@ win32_set_exception_list(p) | |
| int rb_thread_pending = 0; | |
| VALUE rb_cThread; | |
| +static VALUE rb_cFiber; | |
| +static VALUE rb_cRootFiber; | |
| +static VALUE rb_eFiberError; | |
| +static rb_thread_t curr_fiber; | |
| +static VALUE root_fiber; | |
| extern VALUE rb_last_status; | |
| @@ -10367,14 +10380,19 @@ timeofday() | |
| return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6; | |
| } | |
| -#define STACK(addr) (th->stk_pos<(VALUE*)(addr) && (VALUE*)(addr)<th->stk_pos+th->stk_len) | |
| -#define ADJ(addr) (void*)(STACK(addr)?(((VALUE*)(addr)-th->stk_pos)+th->stk_ptr):(VALUE*)(addr)) | |
| + | |
| +#define ADJ(addr) \ | |
| + if ((size_t)((void *)addr - stkBase) < stkSize) addr=(void *)addr + stkShift | |
| + | |
| static void | |
| thread_mark(th) | |
| rb_thread_t th; | |
| { | |
| struct FRAME *frame; | |
| struct BLOCK *block; | |
| + void *stkBase; | |
| + ptrdiff_t stkShift; | |
| + size_t stkSize; | |
| rb_gc_mark(th->result); | |
| rb_gc_mark(th->thread); | |
| @@ -10394,9 +10412,14 @@ thread_mark(th) | |
| rb_gc_mark(th->thgroup); | |
| rb_gc_mark_maybe(th->sandbox); | |
| + if (th->fiber_return) thread_mark(th->fiber_return); | |
| + rb_gc_mark_maybe(th->fiber_value); | |
| + | |
| /* mark data in copied stack */ | |
| if (th == curr_thread) return; | |
| + if (th == curr_fiber) return; | |
| if (th->status == THREAD_KILLED) return; | |
| + if (th->fiber_status == FIBER_KILLED) return; | |
| if (th->stk_len == 0) return; /* stack not active, no need to mark. */ | |
| if (th->stk_ptr) { | |
| rb_gc_mark_locations(th->stk_ptr, th->stk_ptr+th->stk_len); | |
| @@ -10409,15 +10432,26 @@ thread_mark(th) | |
| } | |
| #endif | |
| } | |
| + | |
| + stkBase = (void *)th->stk_start; | |
| + stkSize = th->stk_len * sizeof(VALUE); | |
| +#if STACK_GROW_DIRECTION == 0 | |
| + if ((VALUE *)&th < rb_gc_stack_start) | |
| +#endif | |
| +#if STACK_GROW_DIRECTION <= 0 | |
| + stkBase -= stkSize; | |
| +#endif | |
| + stkShift = (void *)th->stk_ptr - stkBase; | |
| + | |
| frame = th->frame; | |
| while (frame && frame != top_frame) { | |
| - frame = ADJ(frame); | |
| + ADJ(frame); | |
| rb_gc_mark_frame(frame); | |
| if (frame->tmp) { | |
| struct FRAME *tmp = frame->tmp; | |
| while (tmp && tmp != top_frame) { | |
| - tmp = ADJ(tmp); | |
| + ADJ(tmp); | |
| rb_gc_mark_frame(tmp); | |
| tmp = tmp->prev; | |
| } | |
| @@ -10426,7 +10460,7 @@ thread_mark(th) | |
| } | |
| block = th->block; | |
| while (block) { | |
| - block = ADJ(block); | |
| + ADJ(block); | |
| rb_gc_mark_frame(&block->frame); | |
| block = block->prev; | |
| } | |
| @@ -10485,6 +10519,24 @@ rb_gc_abort_threads() | |
| } END_FOREACH_FROM(main_thread, th); | |
| } | |
| +static inline void | |
| +stack_free(th) | |
| + rb_thread_t th; | |
| +{ | |
| + if (th->stk_ptr) { | |
| + free(th->stk_ptr); | |
| + th->stk_ptr = 0; | |
| + th->stk_max = 0; | |
| + th->stk_len = 0; | |
| + } | |
| +#ifdef __ia64 | |
| + if (th->bstr_ptr) { | |
| + free(th->bstr_ptr); | |
| + th->bstr_ptr = 0; | |
| + } | |
| +#endif | |
| +} | |
| + | |
| static void | |
| thread_free(th) | |
| rb_thread_t th; | |
| @@ -10495,6 +10547,7 @@ thread_free(th) | |
| if (th->bstr_ptr) free(th->bstr_ptr); | |
| th->bstr_ptr = 0; | |
| #endif | |
| + if (th->fiber_return) thread_free(th->fiber_return); | |
| if (th->locals) st_free_table(th->locals); | |
| if (th->status != THREAD_KILLED) { | |
| if (th->prev) th->prev->next = th->next; | |
| @@ -10538,13 +10591,10 @@ static void | |
| rb_thread_save_context(th) | |
| rb_thread_t th; | |
| { | |
| - VALUE *pos; | |
| int len; | |
| static VALUE tval; | |
| - len = ruby_stack_length(&pos); | |
| - th->stk_len = 0; | |
| - th->stk_pos = pos; | |
| + len = ruby_stack_length(th->stk_start,&th->stk_pos); | |
| if (len > th->stk_max) { | |
| VALUE *ptr = realloc(th->stk_ptr, sizeof(VALUE) * len); | |
| if (!ptr) rb_memerror(); | |
| @@ -10646,11 +10696,10 @@ rb_thread_switch(n) | |
| (rb_thread_switch(ruby_setjmp(rb_thread_save_context(th), (th)->context))) | |
| NORETURN(static void rb_thread_restore_context _((rb_thread_t,int))); | |
| -NORETURN(NOINLINE(static void rb_thread_restore_context_0(rb_thread_t,int,void*))); | |
| -NORETURN(NOINLINE(static void stack_extend(rb_thread_t, int, VALUE *))); | |
| +NORETURN(NOINLINE(static void rb_thread_restore_context_0(rb_thread_t,int))); | |
| static void | |
| -rb_thread_restore_context_0(rb_thread_t th, int exit, void *vp) | |
| +rb_thread_restore_context_0(rb_thread_t th, int exit) | |
| { | |
| static rb_thread_t tmp; | |
| static int ex; | |
| @@ -10707,9 +10756,9 @@ static volatile int C(f), C(g), C(h), C(i), C(j); | |
| static volatile int C(k), C(l), C(m), C(n), C(o); | |
| static volatile int C(p), C(q), C(r), C(s), C(t); | |
| int rb_dummy_false = 0; | |
| -NORETURN(NOINLINE(static void register_stack_extend(rb_thread_t, int, void *, VALUE *))); | |
| +NORETURN(NOINLINE(static void register_stack_extend(rb_thread_t, int, VALUE *))); | |
| static void | |
| -register_stack_extend(rb_thread_t th, int exit, void *vp, VALUE *curr_bsp) | |
| +register_stack_extend(rb_thread_t th, int exit, VALUE *curr_bsp) | |
| { | |
| if (rb_dummy_false) { | |
| /* use registers as much as possible */ | |
| @@ -10723,52 +10772,68 @@ register_stack_extend(rb_thread_t th, int exit, void *vp, VALUE *curr_bsp) | |
| E(p) = E(q) = E(r) = E(s) = E(t) = 0; | |
| } | |
| if (curr_bsp < th->bstr_pos+th->bstr_len) { | |
| - register_stack_extend(th, exit, &exit, (VALUE*)rb_ia64_bsp()); | |
| + register_stack_extend(th, exit, (VALUE*)rb_ia64_bsp()); | |
| } | |
| - rb_thread_restore_context_0(th, exit, &exit); | |
| + rb_thread_restore_context_0(th, exit); | |
| } | |
| #undef C | |
| #undef E | |
| #endif | |
| -# if defined(_MSC_VER) && _MSC_VER >= 1300 | |
| -__declspec(noinline) static void stack_extend(rb_thread_t, int, VALUE*); | |
| -# endif | |
| -static void | |
| -stack_extend(rb_thread_t th, int exit, VALUE *addr_in_prev_frame) | |
| -{ | |
| -#define STACK_PAD_SIZE 1024 | |
| - VALUE space[STACK_PAD_SIZE]; | |
| - | |
| -#if STACK_GROW_DIRECTION < 0 | |
| - if (addr_in_prev_frame > th->stk_pos) stack_extend(th, exit, &space[0]); | |
| -#elif STACK_GROW_DIRECTION > 0 | |
| - if (addr_in_prev_frame < th->stk_pos + th->stk_len) stack_extend(th, exit, &space[STACK_PAD_SIZE-1]); | |
| -#else | |
| - if (addr_in_prev_frame < rb_gc_stack_start) { | |
| - /* Stack grows downward */ | |
| - if (addr_in_prev_frame > th->stk_pos) stack_extend(th, exit, &space[0]); | |
| - } | |
| - else { | |
| - /* Stack grows upward */ | |
| - if (addr_in_prev_frame < th->stk_pos + th->stk_len) stack_extend(th, exit, &space[STACK_PAD_SIZE-1]); | |
| - } | |
| -#endif | |
| -#ifdef __ia64 | |
| - register_stack_extend(th, exit, space, (VALUE*)rb_ia64_bsp()); | |
| -#else | |
| - rb_thread_restore_context_0(th, exit, space); | |
| -#endif | |
| -} | |
| static void | |
| rb_thread_restore_context(th, exit) | |
| rb_thread_t th; | |
| int exit; | |
| { | |
| + VALUE *pos = th->stk_start; | |
| + | |
| +#if HAVE_ALLOCA /* use alloca to grow stack in O(1) time */ | |
| VALUE v; | |
| + volatile VALUE *space; | |
| + | |
| + if (!th->stk_ptr) rb_bug("unsaved context"); | |
| +# if !STACK_GROW_DIRECTION /* unknown at compile time */ | |
| + if (rb_gc_stack_grow_direction < 0) { | |
| +# endif | |
| +# if STACK_GROW_DIRECTION <= 0 | |
| + pos -= th->stk_len; | |
| + if (&v > pos) space=ALLOCA_N(VALUE, &v-pos); | |
| +# endif | |
| +# if !STACK_GROW_DIRECTION | |
| + }else | |
| +# endif | |
| +#if STACK_GROW_DIRECTION >= 0 /* stack grows upward */ | |
| + if (&v < pos + th->stk_len) space=ALLOCA_N(VALUE, pos+th->stk_len - &v); | |
| +# endif | |
| + | |
| +#else /* recursive O(n/1024) if extending stack > 1024 VALUEs */ | |
| + | |
| + volatile VALUE v[1023]; | |
| + | |
| +# if !STACK_GROW_DIRECTION /* unknown at compile time */ | |
| + if (rb_gc_stack_grow_direction < 0) { | |
| +# endif | |
| +# if STACK_GROW_DIRECTION <= 0 | |
| + pos -= th->stk_len; | |
| + if (v > pos) rb_thread_restore_context(th, exit); | |
| +# endif | |
| +# if !STACK_GROW_DIRECTION | |
| + }else | |
| +# endif | |
| +# if STACK_GROW_DIRECTION >= 0 /* stack grows upward */ | |
| + if (v < pos + th->stk_len) rb_thread_restore_context(th, exit); | |
| +# endif | |
| if (!th->stk_ptr) rb_bug("unsaved context"); | |
| - stack_extend(th, exit, &v); | |
| + | |
| +#endif /* stack now extended */ | |
| + | |
| + | |
| +#ifdef __ia64 | |
| + register_stack_extend(th, exit, (VALUE*)rb_ia64_bsp()); | |
| +#else | |
| + rb_thread_restore_context_0(th, exit); | |
| +#endif | |
| } | |
| static void | |
| @@ -12034,6 +12099,7 @@ rb_thread_group(thread) | |
| th->result = 0;\ | |
| th->flags = 0;\ | |
| \ | |
| + th->stk_start = rb_gc_stack_start;\ | |
| th->stk_ptr = 0;\ | |
| th->stk_len = 0;\ | |
| th->stk_max = 0;\ | |
| @@ -12066,6 +12132,11 @@ rb_thread_group(thread) | |
| th->thgroup = thgroup_default;\ | |
| th->locals = 0;\ | |
| th->thread = 0;\ | |
| + th->fiber_status = FIBER_NONE;\ | |
| + th->fiber_value = Qnil;\ | |
| + th->fiber_self = Qnil;\ | |
| + th->fiber_return = 0;\ | |
| + th->fiber_error = Qnil;\ | |
| if (curr_thread == 0) {\ | |
| th->sandbox = Qnil;\ | |
| } else {\ | |
| @@ -12203,6 +12274,16 @@ rb_thread_start_0(fn, arg, th) | |
| "can't start a new thread (frozen ThreadGroup)"); | |
| } | |
| + | |
| + th->stk_start = /* establish start of new thread's stack */ | |
| +#if STACK_GROW_DIRECTION > 0 | |
| + (VALUE *)ruby_frame; | |
| +#elif STACK_GROW_DIRECTION < 0 | |
| + (VALUE *)(ruby_frame+1); | |
| +#else | |
| + (VALUE *)(ruby_frame+((VALUE *)(&arg)<rb_gc_stack_start)) | |
| +#endif | |
| + | |
| if (!thread_init) { | |
| thread_init = 1; | |
| #if defined(HAVE_SETITIMER) || defined(_THREAD_SAFE) | |
| @@ -12246,6 +12327,8 @@ rb_thread_start_0(fn, arg, th) | |
| PUSH_TAG(PROT_THREAD); | |
| if ((state = EXEC_TAG()) == 0) { | |
| if (THREAD_SAVE_CONTEXT(th) == 0) { | |
| + ruby_frame->prev = top_frame; /* hide parent thread's frames */ | |
| + ruby_frame->tmp = 0; | |
| curr_thread = th; | |
| th->result = (*fn)(arg, th); | |
| } | |
| @@ -12357,9 +12440,6 @@ rb_thread_s_new(argc, argv, klass) | |
| VALUE klass; | |
| { | |
| rb_thread_t th = rb_thread_alloc(klass); | |
| - volatile VALUE *pos; | |
| - | |
| - pos = th->stk_pos; | |
| rb_obj_call_init(th->thread, argc, argv); | |
| if (th->stk_pos == 0) { | |
| rb_raise(rb_eThreadError, "uninitialized thread - check `%s#initialize'", | |
| @@ -13048,6 +13128,7 @@ rb_callcc(self) | |
| scope_dup(ruby_scope); | |
| for (tag=prot_tag; tag; tag=tag->prev) { | |
| + if (tag->tag == PROT_THREAD) break; | |
| scope_dup(tag->scope); | |
| } | |
| th->thread = curr_thread->thread; | |
| @@ -13395,6 +13476,255 @@ rb_exec_recursive(func, obj, arg) | |
| } | |
| } | |
| +static VALUE | |
| +make_passing_arg(argc, argv) | |
| + int argc; | |
| + VALUE *argv; | |
| +{ | |
| + switch(argc) { | |
| + case 0: | |
| + return rb_ary_new2(0); | |
| + case 1: | |
| + return argv[0]; | |
| + default: | |
| + return rb_ary_new4(argc, argv); | |
| + } | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_init(self) | |
| + VALUE self; | |
| +{ | |
| + if (!rb_block_given_p()) | |
| + rb_raise(rb_eArgError, "new Fiber requires a block"); | |
| + | |
| + volatile rb_thread_t fib; | |
| + Data_Get_Struct(self, struct rb_thread, fib); | |
| + | |
| + fib->fiber_thread = curr_thread->thread; | |
| + | |
| +#if STACK_GROW_DIRECTION > 0 | |
| + fib->stk_start = (VALUE *)ruby_frame; | |
| +#elif STACK_GROW_DIRECTION < 0 | |
| + fib->stk_start = (VALUE *)(ruby_frame+1); | |
| +#else | |
| + fib->stk_start = (VALUE *)(ruby_frame+((VALUE *)(&fib)<rb_gc_stack_start)) | |
| +#endif | |
| + | |
| + if (THREAD_SAVE_CONTEXT(fib)) { | |
| + // setup the fiber | |
| + struct BLOCK *volatile saved_block = 0; | |
| + struct BLOCK dummy; | |
| + struct RVarmap *vars; | |
| + struct tag *tag; | |
| + | |
| + // dup current ruby_block, free all others | |
| + dummy.prev = ruby_block; | |
| + blk_copy_prev(&dummy); | |
| + ruby_block = saved_block = dummy.prev; | |
| + | |
| + scope_dup(ruby_scope); | |
| + | |
| + for (tag=prot_tag; tag; tag=tag->prev) { | |
| + if(tag->tag == PROT_THREAD || tag->tag == PROT_FIBER) break; | |
| + scope_dup(tag->scope); | |
| + } | |
| + | |
| + for (vars = ruby_dyna_vars; vars; vars = vars->next) { | |
| + if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; | |
| + FL_SET(vars, DVAR_DONT_RECYCLE); | |
| + } | |
| + | |
| + fib->fiber_status = FIBER_CREATED; | |
| + fib->fiber_value = Qnil; | |
| + | |
| + ruby_frame->prev = top_frame; | |
| + ruby_frame->tmp = 0; | |
| + | |
| + // fiber is ready, jump back and return it | |
| + if (!THREAD_SAVE_CONTEXT(fib)) | |
| + rb_thread_restore_context(fib->fiber_return, RESTORE_NORMAL); | |
| + | |
| + // start the fiber | |
| + int state; | |
| + | |
| + PUSH_TAG(PROT_FIBER); | |
| + if ((state = EXEC_TAG()) == 0) { | |
| + fib->fiber_status = FIBER_RUNNING; | |
| + fib->fiber_value = rb_yield(fib->fiber_value); | |
| + } | |
| + POP_TAG(); | |
| + | |
| + fib->fiber_error = ruby_errinfo; | |
| + fib->fiber_status = FIBER_KILLED; | |
| + | |
| + blk_free(saved_block); | |
| + | |
| + stack_free(fib); | |
| + | |
| + rb_thread_restore_context(fib->fiber_return, RESTORE_NORMAL); | |
| + } | |
| + | |
| + // jump into the fiber initially to set it up | |
| + if (!THREAD_SAVE_CONTEXT(fib->fiber_return)) | |
| + rb_thread_restore_context(fib, RESTORE_NORMAL); | |
| + | |
| + stack_free(fib->fiber_return); | |
| + | |
| + return self; | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_resume(argc, argv, self) | |
| + int argc; | |
| + VALUE *argv; | |
| + VALUE self; | |
| +{ | |
| + rb_thread_t fib, prev_fiber; | |
| + Data_Get_Struct(self, struct rb_thread, fib); | |
| + | |
| + if (fib->fiber_status == FIBER_KILLED) { | |
| + rb_raise(rb_eFiberError, "dead fiber called"); | |
| + } | |
| + | |
| + if (fib->fiber_status == FIBER_RUNNING) { | |
| + rb_raise(rb_eFiberError, "double resume"); | |
| + } | |
| + | |
| + if (fib->fiber_thread != curr_thread->thread) { | |
| + rb_raise(rb_eFiberError, "fiber called across threads"); | |
| + } | |
| + | |
| + prev_fiber = curr_fiber; | |
| + if (!THREAD_SAVE_CONTEXT(fib->fiber_return)) { | |
| + fib->fiber_value = make_passing_arg(argc, argv); | |
| + curr_fiber = fib; | |
| + fib->fiber_status = FIBER_RUNNING; | |
| + rb_thread_restore_context(fib, RESTORE_NORMAL); | |
| + } | |
| + | |
| + stack_free(fib->fiber_return); | |
| + | |
| + if (fib->fiber_status != FIBER_KILLED) | |
| + fib->fiber_status = FIBER_STOPPED; | |
| + | |
| + curr_fiber = prev_fiber; | |
| + | |
| + if (fib->fiber_error != Qnil) { | |
| + rb_exc_raise(fib->fiber_error); | |
| + fib->fiber_error = Qnil; | |
| + } | |
| + | |
| + return fib->fiber_value; | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_yield(argc, argv, self) | |
| + int argc; | |
| + VALUE *argv; | |
| + VALUE self; | |
| +{ | |
| + rb_thread_t fib; | |
| + Data_Get_Struct(self, struct rb_thread, fib); | |
| + | |
| + if (!THREAD_SAVE_CONTEXT(fib)) { | |
| + fib->fiber_value = make_passing_arg(argc, argv); | |
| + rb_thread_restore_context(fib->fiber_return, RESTORE_NORMAL); | |
| + } | |
| + | |
| + return fib->fiber_value; | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_s_current(klass) | |
| + VALUE klass; | |
| +{ | |
| + if (!curr_fiber) | |
| + return root_fiber; | |
| + | |
| + return curr_fiber->fiber_self; | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_s_yield(argc, argv, self) | |
| + int argc; | |
| + VALUE *argv; | |
| + VALUE self; | |
| +{ | |
| + if (!curr_fiber) { | |
| + rb_raise(rb_eFiberError, "can't yield from root fiber"); | |
| + } | |
| + | |
| + VALUE fib = curr_fiber->fiber_self; | |
| + if (fib == Qnil) { | |
| + rb_raise(rb_eFiberError, "not inside a fiber"); | |
| + } | |
| + else { | |
| + return rb_fiber_yield(argc, argv, fib); | |
| + } | |
| +} | |
| + | |
| +static VALUE | |
| +rb_fiber_alive_p(self) | |
| + VALUE self; | |
| +{ | |
| + rb_thread_t fib; | |
| + Data_Get_Struct(self, struct rb_thread, fib); | |
| + return fib->fiber_status > FIBER_KILLED; | |
| +} | |
| + | |
| +static VALUE | |
| +fiber_alloc(klass) | |
| + VALUE klass; | |
| +{ | |
| + rb_thread_t fib; | |
| + THREAD_ALLOC(fib); | |
| + THREAD_ALLOC(fib->fiber_return); | |
| + fib->fiber_self = Data_Wrap_Struct(klass, thread_mark, thread_free, fib); | |
| + return fib->fiber_self; | |
| +} | |
| + | |
| +static VALUE | |
| +rb_root_fiber_resume(argc, argv, self) | |
| + int argc; | |
| + VALUE *argv; | |
| + VALUE self; | |
| +{ | |
| + rb_raise(rb_eFiberError, "can't resume root fiber"); | |
| +} | |
| + | |
| +static VALUE | |
| +rb_root_fiber_alloc() | |
| +{ | |
| + if (!root_fiber) { | |
| + rb_gc_register_address(&root_fiber); | |
| + root_fiber = Data_Wrap_Struct(rb_cFiber, 0, 0, 0); | |
| + rb_define_singleton_method(root_fiber, "resume", rb_root_fiber_resume, -1); | |
| + } | |
| + return root_fiber; | |
| +} | |
| + | |
| +void | |
| +Init_Fiber() | |
| +{ | |
| + rb_cFiber = rb_define_class("Fiber", rb_cObject); | |
| + rb_define_alloc_func(rb_cFiber, fiber_alloc); | |
| + rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); | |
| + rb_define_singleton_method(rb_cFiber, "yield", rb_fiber_s_yield, -1); | |
| + rb_define_method(rb_cFiber, "initialize", rb_fiber_init, 0); | |
| + rb_define_method(rb_cFiber, "resume", rb_fiber_resume, -1); | |
| + | |
| + rb_root_fiber_alloc(); | |
| + curr_fiber = NULL; | |
| +} | |
| + | |
| +void | |
| +ruby_Init_Fiber_as_Coroutine() | |
| +{ | |
| + //rb_define_method(rb_cFiber, "transfer", rb_fiber_transfer, -1); | |
| + rb_define_method(rb_cFiber, "alive?", rb_fiber_alive_p, 0); | |
| + rb_define_singleton_method(rb_cFiber, "current", rb_fiber_s_current, 0); | |
| +} | |
| /* | |
| * +Thread+ encapsulates the behavior of a thread of | |
| @@ -13485,6 +13815,7 @@ Init_Thread() | |
| /* allocate main thread */ | |
| main_thread = rb_thread_alloc(rb_cThread); | |
| curr_thread = main_thread->prev = main_thread->next = main_thread; | |
| + Init_Fiber(); | |
| } | |
| /* | |
| @@ -13580,7 +13911,7 @@ rb_f_throw(argc, argv) | |
| tag = ID2SYM(rb_to_id(tag)); | |
| while (tt) { | |
| - if (tt->tag == tag) { | |
| + if (tt->tag == tag || tt->tag == PROT_FIBER) { | |
| tt->dst = tag; | |
| tt->retval = value; | |
| break; | |
| @@ -13592,7 +13923,7 @@ rb_f_throw(argc, argv) | |
| } | |
| tt = tt->prev; | |
| } | |
| - if (!tt) { | |
| + if (!tt || tt->tag == PROT_FIBER) { | |
| rb_name_error(SYM2ID(tag), "uncaught throw `%s'", rb_id2name(SYM2ID(tag))); | |
| } | |
| rb_trap_restore_mask(); | |
| diff --git a/ext/fiber/extconf.rb b/ext/fiber/extconf.rb | |
| new file mode 100644 | |
| index 0000000..904ab94 | |
| --- /dev/null | |
| +++ b/ext/fiber/extconf.rb | |
| @@ -0,0 +1,3 @@ | |
| +require 'mkmf' | |
| +create_makefile('fiber') | |
| + | |
| diff --git a/ext/fiber/fiber.c b/ext/fiber/fiber.c | |
| new file mode 100644 | |
| index 0000000..12fcaad | |
| --- /dev/null | |
| +++ b/ext/fiber/fiber.c | |
| @@ -0,0 +1,8 @@ | |
| + | |
| +void ruby_Init_Fiber_as_Coroutine(void); | |
| + | |
| +void | |
| +Init_fiber(void) | |
| +{ | |
| + ruby_Init_Fiber_as_Coroutine(); | |
| +} | |
| diff --git a/gc.c b/gc.c | |
| index 45facf0..f881b9b 100644 | |
| --- a/gc.c | |
| +++ b/gc.c | |
| @@ -2,8 +2,8 @@ | |
| gc.c - | |
| - $Author: shyouhei $ | |
| - $Date: 2008-08-04 12:24:26 +0900 (Mon, 04 Aug 2008) $ | |
| + $Author: brent $ | |
| + $Date: 2008/12/13 05:47:46 $ | |
| created at: Tue Oct 5 09:44:46 JST 1993 | |
| Copyright (C) 1993-2003 Yukihiro Matsumoto | |
| @@ -506,12 +506,12 @@ stack_end_address(VALUE **stack_end_p) | |
| # define STACK_END (stack_end) | |
| #endif | |
| #if STACK_GROW_DIRECTION < 0 | |
| -# define STACK_LENGTH (rb_gc_stack_start - STACK_END) | |
| +# define STACK_LENGTH(start) ((start) - STACK_END) | |
| #elif STACK_GROW_DIRECTION > 0 | |
| -# define STACK_LENGTH (STACK_END - rb_gc_stack_start + 1) | |
| +# define STACK_LENGTH(start) (STACK_END - (start) + 1) | |
| #else | |
| -# define STACK_LENGTH ((STACK_END < rb_gc_stack_start) ? rb_gc_stack_start - STACK_END\ | |
| - : STACK_END - rb_gc_stack_start + 1) | |
| +# define STACK_LENGTH(start) ((STACK_END < (start)) ? (start) - STACK_END\ | |
| + : STACK_END - (start) + 1) | |
| #endif | |
| #if STACK_GROW_DIRECTION > 0 | |
| # define STACK_UPPER(x, a, b) a | |
| @@ -536,16 +536,16 @@ stack_grow_direction(addr) | |
| #define CHECK_STACK(ret) do {\ | |
| SET_STACK_END;\ | |
| - (ret) = (STACK_LENGTH > STACK_LEVEL_MAX + GC_WATER_MARK);\ | |
| + (ret) = (STACK_LENGTH(rb_gc_stack_start) > STACK_LEVEL_MAX + GC_WATER_MARK);\ | |
| } while (0) | |
| int | |
| -ruby_stack_length(p) | |
| - VALUE **p; | |
| +ruby_stack_length(start, base) | |
| + VALUE *start, **base; | |
| { | |
| SET_STACK_END; | |
| - if (p) *p = STACK_UPPER(STACK_END, rb_gc_stack_start, STACK_END); | |
| - return STACK_LENGTH; | |
| + if (base) *base = STACK_UPPER(STACK_END, start, STACK_END); | |
| + return STACK_LENGTH(start); | |
| } | |
| int | |
| diff --git a/intern.h b/intern.h | |
| index 30da4a4..f0a2526 100644 | |
| --- a/intern.h | |
| +++ b/intern.h | |
| @@ -251,7 +251,7 @@ VALUE rb_file_directory_p _((VALUE,VALUE)); | |
| /* gc.c */ | |
| NORETURN(void rb_memerror __((void))); | |
| int ruby_stack_check _((void)); | |
| -int ruby_stack_length _((VALUE**)); | |
| +int ruby_stack_length _((VALUE *,VALUE**)); | |
| int rb_during_gc _((void)); | |
| char *rb_source_filename _((const char*)); | |
| void rb_gc_mark_locations _((VALUE*, VALUE*)); | |
| diff --git a/lib/continuation.rb b/lib/continuation.rb | |
| new file mode 100644 | |
| index 0000000..ee0a3a4 | |
| --- /dev/null | |
| +++ b/lib/continuation.rb | |
| @@ -0,0 +1 @@ | |
| +# stub to make require continuation work | |
| diff --git a/node.h b/node.h | |
| index 7e039b9..509b1fd 100644 | |
| --- a/node.h | |
| +++ b/node.h | |
| @@ -2,8 +2,8 @@ | |
| node.h - | |
| - $Author: shyouhei $ | |
| - $Date: 2008-07-07 15:17:24 +0900 (Mon, 07 Jul 2008) $ | |
| + $Author: brent $ | |
| + $Date: 2008/12/13 05:02:23 $ | |
| created at: Fri May 28 15:14:02 JST 1993 | |
| Copyright (C) 1993-2003 Yukihiro Matsumoto | |
| @@ -400,6 +400,14 @@ enum rb_thread_status { | |
| typedef struct rb_thread *rb_thread_t; | |
| +enum rb_fiber_status { | |
| + FIBER_NONE, | |
| + FIBER_KILLED, | |
| + FIBER_CREATED, | |
| + FIBER_RUNNING, | |
| + FIBER_STOPPED, | |
| +}; | |
| + | |
| struct rb_thread { | |
| rb_thread_t next, prev; | |
| rb_jmpbuf_t context; | |
| @@ -409,10 +417,8 @@ struct rb_thread { | |
| VALUE result; | |
| - long stk_len; | |
| - long stk_max; | |
| - VALUE *stk_ptr; | |
| - VALUE *stk_pos; | |
| + long stk_len, stk_max; | |
| + VALUE *stk_ptr, *stk_pos, *stk_start; | |
| #ifdef __ia64 | |
| long bstr_len; | |
| long bstr_max; | |
| @@ -461,6 +467,13 @@ struct rb_thread { | |
| VALUE thread; | |
| VALUE sandbox; | |
| + | |
| + enum rb_fiber_status fiber_status; | |
| + VALUE fiber_error; | |
| + VALUE fiber_value; | |
| + VALUE fiber_self; | |
| + VALUE fiber_thread; | |
| + rb_thread_t fiber_return; | |
| }; | |
| extern VALUE (*ruby_sandbox_save)_((rb_thread_t)); | |
| diff --git a/test/test_fiber.rb b/test/test_fiber.rb | |
| new file mode 100644 | |
| index 0000000..efadaeb | |
| --- /dev/null | |
| +++ b/test/test_fiber.rb | |
| @@ -0,0 +1,171 @@ | |
| +require 'test/unit' | |
| +require 'fiber' | |
| +require 'continuation' | |
| + | |
| +class TestFiber < Test::Unit::TestCase | |
| + def test_normal | |
| + f = Fiber.current | |
| + assert_equal(:ok2, | |
| + Fiber.new{|e| | |
| + assert_equal(:ok1, e) | |
| + Fiber.yield :ok2 | |
| + }.resume(:ok1) | |
| + ) | |
| + assert_equal([:a, :b], Fiber.new{|a, b| [a, b]}.resume(:a, :b)) | |
| + end | |
| + | |
| + def test_term | |
| + assert_equal(:ok, Fiber.new{:ok}.resume) | |
| + assert_equal([:a, :b, :c, :d, :e], | |
| + Fiber.new{ | |
| + Fiber.new{ | |
| + Fiber.new{ | |
| + Fiber.new{ | |
| + [:a] | |
| + }.resume + [:b] | |
| + }.resume + [:c] | |
| + }.resume + [:d] | |
| + }.resume + [:e]) | |
| + end | |
| + | |
| + def test_many_fibers | |
| + max = 10000 | |
| + assert_equal(max, max.times{ | |
| + Fiber.new{} | |
| + }) | |
| + assert_equal(max, | |
| + max.times{|i| | |
| + Fiber.new{ | |
| + }.resume | |
| + } | |
| + ) | |
| + end | |
| + | |
| + def test_many_fibers_with_threads | |
| + max = 1000 | |
| + @cnt = 0 | |
| + (1..100).map{|ti| | |
| + Thread.new{ | |
| + max.times{|i| | |
| + Fiber.new{ | |
| + @cnt += 1 | |
| + }.resume | |
| + } | |
| + } | |
| + }.each{|t| | |
| + t.join | |
| + } | |
| + assert_equal(:ok, :ok) | |
| + end | |
| + | |
| + def test_error | |
| + assert_raise(ArgumentError){ | |
| + Fiber.new # Fiber without block | |
| + } | |
| + assert_raise(FiberError){ | |
| + f = Fiber.new{} | |
| + Thread.new{f.resume}.join # Fiber yielding across thread | |
| + } | |
| + assert_raise(FiberError){ | |
| + f = Fiber.new{} | |
| + f.resume | |
| + f.resume | |
| + } | |
| +=begin | |
| + # XXX this behavior is undefined... just say no! | |
| + assert_raise(RuntimeError){ | |
| + f = Fiber.new{ | |
| + @c = callcc{|c| @c = c} | |
| + }.resume | |
| + @c.call # cross fiber callcc | |
| + } | |
| +=end | |
| + assert_raise(RuntimeError){ | |
| + Fiber.new{ | |
| + raise | |
| + }.resume | |
| + } | |
| + 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 | |
| + } | |
| + end | |
| + | |
| + def test_return | |
| + assert_raise(LocalJumpError){ | |
| + Fiber.new do | |
| + return | |
| + end.resume | |
| + } | |
| + end | |
| + | |
| + def test_throw | |
| + assert_raise(NameError){ | |
| + Fiber.new do | |
| + throw :a | |
| + end.resume | |
| + } | |
| + end | |
| + | |
| +=begin | |
| + # XXX what is transfer supposed to do??? | |
| + def test_transfer | |
| + 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 | |
| +=end | |
| + | |
| +=begin | |
| + # XXX this tests to make sure Thread.current inside a fiber is not shared with Thread.current outside the fiber | |
| + def test_tls | |
| + def tvar(var, val) | |
| + old = Thread.current[var] | |
| + begin | |
| + Thread.current[var] = val | |
| + yield | |
| + ensure | |
| + Thread.current[var] = old | |
| + end | |
| + end | |
| + | |
| + fb = Fiber.new { | |
| + assert_equal(nil, Thread.current[:v]); tvar(:v, :x) { | |
| + assert_equal(:x, Thread.current[:v]); Fiber.yield | |
| + assert_equal(:x, Thread.current[:v]); } | |
| + assert_equal(nil, Thread.current[:v]); Fiber.yield | |
| + raise # unreachable | |
| + } | |
| + | |
| + assert_equal(nil, Thread.current[:v]); tvar(:v,1) { | |
| + assert_equal(1, Thread.current[:v]); tvar(:v,3) { | |
| + assert_equal(3, Thread.current[:v]); fb.resume | |
| + assert_equal(3, Thread.current[:v]); } | |
| + assert_equal(1, Thread.current[:v]); } | |
| + assert_equal(nil, Thread.current[:v]); fb.resume | |
| + assert_equal(nil, Thread.current[:v]); | |
| + end | |
| +=end | |
| +end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment