Created
March 31, 2009 02:18
-
-
Save ice799/88010 to your computer and use it in GitHub Desktop.
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
Index: eval.c | |
=================================================================== | |
--- eval.c (revision 23100) | |
+++ eval.c (working copy) | |
@@ -1038,6 +1038,7 @@ | |
#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) | |
@@ -1137,6 +1138,15 @@ | |
} while (0); \ | |
POP_TAG() | |
+#define PUSH_FIBER_TAG() PUSH_TAG(PROT_FIBER); \ | |
+ do { \ | |
+ struct ruby_env _interp; \ | |
+ push_thread_anchor(&_interp); | |
+#define POP_FIBER_TAG() \ | |
+ pop_thread_anchor(&_interp); \ | |
+ } while (0); \ | |
+ POP_TAG() | |
+ | |
static VALUE rb_eval _((VALUE,NODE*)); | |
static VALUE eval _((VALUE,VALUE,VALUE,const char*,int)); | |
static NODE *compile _((VALUE, const char*, int)); | |
@@ -4916,6 +4926,9 @@ | |
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"); | |
} | |
@@ -4937,6 +4950,7 @@ | |
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); | |
@@ -4966,6 +4980,7 @@ | |
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); | |
@@ -5201,6 +5216,7 @@ | |
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); | |
@@ -6642,6 +6658,7 @@ | |
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) { | |
@@ -10298,6 +10315,10 @@ | |
int rb_thread_pending = 0; | |
VALUE rb_cThread; | |
+static VALUE rb_cFiber; | |
+static VALUE rb_eFiberError; | |
+static rb_thread_t curr_fiber; | |
+static VALUE root_fiber; | |
extern VALUE rb_last_status; | |
@@ -10514,6 +10535,7 @@ | |
#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)) | |
+ | |
static void | |
thread_mark(th) | |
rb_thread_t th; | |
@@ -10539,9 +10561,14 @@ | |
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); | |
@@ -10554,6 +10581,7 @@ | |
} | |
#endif | |
} | |
+ | |
frame = th->frame; | |
while (frame && frame != top_frame) { | |
frame = ADJ(frame); | |
@@ -10644,11 +10672,17 @@ | |
stack_free(th) | |
rb_thread_t th; | |
{ | |
- if (th->stk_ptr) free(th->stk_ptr); | |
- th->stk_ptr = 0; | |
+ 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; | |
+ if (th->bstr_ptr) { | |
+ free(th->bstr_ptr); | |
+ th->bstr_ptr = 0; | |
+ } | |
#endif | |
} | |
@@ -10657,6 +10691,7 @@ | |
rb_thread_t th; | |
{ | |
stack_free(th); | |
+ 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; | |
@@ -12307,6 +12342,11 @@ | |
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;\ | |
th->anchor = 0;\ | |
if (curr_thread == 0) {\ | |
th->sandbox = Qnil;\ | |
@@ -12700,9 +12740,6 @@ | |
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'", | |
@@ -13781,7 +13818,248 @@ | |
} | |
} | |
+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 (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_FIBER_TAG(); | |
+ if ((state = EXEC_TAG()) == 0) { | |
+ fib->fiber_status = FIBER_RUNNING; | |
+ fib->fiber_value = rb_yield(fib->fiber_value); | |
+ } | |
+ POP_FIBER_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 | |
* execution, including the main thread of the Ruby script. | |
@@ -13863,6 +14141,7 @@ | |
/* allocate main thread */ | |
main_thread = rb_thread_alloc(rb_cThread); | |
curr_thread = main_thread->prev = main_thread->next = main_thread; | |
+ Init_Fiber(); | |
} | |
/* | |
@@ -13958,7 +14237,7 @@ | |
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; | |
@@ -13970,7 +14249,7 @@ | |
} | |
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(); | |
Index: node.h | |
=================================================================== | |
--- node.h (revision 23100) | |
+++ node.h (working copy) | |
@@ -400,6 +400,14 @@ | |
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; | |
@@ -463,6 +471,12 @@ | |
VALUE sandbox; | |
struct ruby_env *anchor; | |
+ 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)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment