Skip to content

Instantly share code, notes, and snippets.

@tmm1
Created January 26, 2009 02:56
Show Gist options
  • Select an option

  • Save tmm1/52671 to your computer and use it in GitHub Desktop.

Select an option

Save tmm1/52671 to your computer and use it in GitHub Desktop.
Fiber implementation for Ruby 1.8.7
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