Skip to content

Instantly share code, notes, and snippets.

@ice799
Created March 31, 2009 02:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ice799/88010 to your computer and use it in GitHub Desktop.
Save ice799/88010 to your computer and use it in GitHub Desktop.
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