2 #include <mruby/array.h>
3 #include <mruby/class.h>
4 #include <mruby/proc.h>
6 #define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
8 #define FIBER_STACK_INIT_SIZE 64
9 #define FIBER_CI_INIT_SIZE 8
10 #define CI_ACC_RESUMED -3
14 * Fiber.new{...} -> obj
16 * Creates a fiber, whose execution is suspend until it is explicitly
17 * resumed using <code>Fiber#resume</code> method.
18 * The code running inside the fiber can give up control by calling
19 * <code>Fiber.yield</code> in which case it yields control back to caller
20 * (the caller of the <code>Fiber#resume</code>).
22 * Upon yielding or termination the Fiber returns the value of the last
27 * fiber = Fiber.new do
40 * resuming dead fiber (FiberError)
42 * The <code>Fiber#resume</code> method accepts an arbitrary number of
43 * parameters, if it is the first call to <code>resume</code> then they
44 * will be passed as block arguments. Otherwise they will be the return
45 * value of the call to <code>Fiber.yield</code>
49 * fiber = Fiber.new do |first|
50 * second = Fiber.yield first + 2
53 * puts fiber.resume 10
54 * puts fiber.resume 14
55 * puts fiber.resume 18
61 * resuming dead fiber (FiberError)
65 fiber_init(mrb_state *mrb, mrb_value self)
67 static const struct mrb_context mrb_context_zero = { 0 };
68 struct RFiber *f = fiber_ptr(self);
69 struct mrb_context *c;
75 mrb_get_args(mrb, "&", &blk);
78 mrb_raise(mrb, E_RUNTIME_ERROR, "cannot initialize twice");
81 mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber object without a block");
83 p = mrb_proc_ptr(blk);
84 if (MRB_PROC_CFUNC_P(p)) {
85 mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
88 c = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
89 *c = mrb_context_zero;
92 /* initialize VM stack */
93 slen = FIBER_STACK_INIT_SIZE;
94 if (p->body.irep->nregs > slen) {
95 slen += p->body.irep->nregs;
97 c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
98 c->stend = c->stbase + slen;
101 #ifdef MRB_NAN_BOXING
103 mrb_value *p = c->stbase;
104 mrb_value *pend = c->stend;
112 memset(c->stbase, 0, slen * sizeof(mrb_value));
115 /* copy receiver from a block */
116 c->stack[0] = mrb->c->stack[0];
118 /* initialize callinfo stack */
119 c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
120 c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
122 c->ci->stackent = c->stack;
124 /* adjust return callinfo */
126 ci->target_class = MRB_PROC_TARGET_CLASS(p);
128 mrb_field_write_barrier(mrb, (struct RBasic*)mrb_obj_ptr(self), (struct RBasic*)p);
129 ci->pc = p->body.irep->iseq;
131 c->ci++; /* push dummy callinfo */
134 c->status = MRB_FIBER_CREATED;
139 static struct mrb_context*
140 fiber_check(mrb_state *mrb, mrb_value fib)
142 struct RFiber *f = fiber_ptr(fib);
144 mrb_assert(f->tt == MRB_TT_FIBER);
146 mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
152 fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
154 if (len == 0) return mrb_nil_value();
155 if (len == 1) return a[0];
156 return mrb_ary_new_from_values(mrb, len, a);
159 /* mark return from context modifying method */
160 #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
163 fiber_check_cfunc(mrb_state *mrb, struct mrb_context *c)
167 for (ci = c->ci; ci >= c->cibase; ci--) {
169 mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
175 fiber_switch_context(mrb_state *mrb, struct mrb_context *c)
178 mrb_write_barrier(mrb, (struct RBasic*)mrb->c->fib);
180 c->status = MRB_FIBER_RUNNING;
185 fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume, mrb_bool vmexec)
187 struct mrb_context *c = fiber_check(mrb, self);
188 struct mrb_context *old_c = mrb->c;
189 enum mrb_fiber_state status;
192 fiber_check_cfunc(mrb, c);
194 if (resume && status == MRB_FIBER_TRANSFERRED) {
195 mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
197 if (status == MRB_FIBER_RUNNING || status == MRB_FIBER_RESUMED) {
198 mrb_raise(mrb, E_FIBER_ERROR, "double resume");
200 if (status == MRB_FIBER_TERMINATED) {
201 mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
203 old_c->status = resume ? MRB_FIBER_RESUMED : MRB_FIBER_TRANSFERRED;
204 c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
205 fiber_switch_context(mrb, c);
206 if (status == MRB_FIBER_CREATED) {
210 mrb_raise(mrb, E_FIBER_ERROR, "double resume (current)");
212 mrb_stack_extend(mrb, len+2); /* for receiver and (optional) block */
218 c->cibase->argc = (int)len;
219 value = c->stack[0] = MRB_PROC_ENV(c->ci->proc)->stack[0];
222 value = fiber_result(mrb, a, len);
227 value = mrb_vm_exec(mrb, c->ci[-1].proc, c->ci->pc);
231 MARK_CONTEXT_MODIFY(c);
238 * fiber.resume(args, ...) -> obj
240 * Resumes the fiber from the point at which the last <code>Fiber.yield</code>
241 * was called, or starts running it if it is the first call to
242 * <code>resume</code>. Arguments passed to resume will be the value of
243 * the <code>Fiber.yield</code> expression or will be passed as block
244 * parameters to the fiber's block if this is the first <code>resume</code>.
246 * Alternatively, when resume is called it evaluates to the arguments passed
247 * to the next <code>Fiber.yield</code> statement inside the fiber's block
248 * or to the block value if it runs to completion without any
249 * <code>Fiber.yield</code>
252 fiber_resume(mrb_state *mrb, mrb_value self)
256 mrb_bool vmexec = FALSE;
258 mrb_get_args(mrb, "*!", &a, &len);
259 if (mrb->c->ci->acc < 0) {
262 return fiber_switch(mrb, self, len, a, TRUE, vmexec);
265 /* resume thread with given arguments */
267 mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
269 return fiber_switch(mrb, fib, len, a, TRUE, TRUE);
274 * fiber.alive? -> true or false
276 * Returns true if the fiber can still be resumed. After finishing
277 * execution of the fiber block this method will always return false.
280 mrb_fiber_alive_p(mrb_state *mrb, mrb_value self)
282 struct mrb_context *c = fiber_check(mrb, self);
283 return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
285 #define fiber_alive_p mrb_fiber_alive_p
288 fiber_eq(mrb_state *mrb, mrb_value self)
291 mrb_get_args(mrb, "o", &other);
293 if (mrb_type(other) != MRB_TT_FIBER) {
294 return mrb_false_value();
296 return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
301 * fiber.transfer(args, ...) -> obj
303 * Transfers control to receiver fiber of the method call.
304 * Unlike <code>resume</code> the receiver wouldn't be pushed to call
305 * stack of fibers. Instead it will switch to the call stack of
306 * transferring fiber.
307 * When resuming a fiber that was transferred to another fiber it would
308 * cause double resume error. Though when the fiber is re-transferred
309 * and <code>Fiber.yield</code> is called, the fiber would be resumable.
312 fiber_transfer(mrb_state *mrb, mrb_value self)
314 struct mrb_context *c = fiber_check(mrb, self);
318 fiber_check_cfunc(mrb, mrb->c);
319 mrb_get_args(mrb, "*!", &a, &len);
321 if (c == mrb->root_c) {
322 mrb->c->status = MRB_FIBER_TRANSFERRED;
323 fiber_switch_context(mrb, c);
324 MARK_CONTEXT_MODIFY(c);
325 return fiber_result(mrb, a, len);
329 return fiber_result(mrb, a, len);
332 return fiber_switch(mrb, self, len, a, FALSE, FALSE);
335 /* yield values to the caller fiber */
336 /* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
338 mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
340 struct mrb_context *c = mrb->c;
343 mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
346 fiber_check_cfunc(mrb, c);
347 c->prev->status = MRB_FIBER_RUNNING;
348 c->status = MRB_FIBER_SUSPENDED;
349 fiber_switch_context(mrb, c->prev);
353 mrb->c->ci->acc = CI_ACC_RESUMED;
355 MARK_CONTEXT_MODIFY(mrb->c);
356 return fiber_result(mrb, a, len);
361 * Fiber.yield(args, ...) -> obj
363 * Yields control back to the context that resumed the fiber, passing
364 * along any arguments that were passed to it. The fiber will resume
365 * processing at this point when <code>resume</code> is called next.
366 * Any arguments passed to the next <code>resume</code> will be the
368 * mruby limitation: Fiber resume/yield cannot cross C function boundary.
369 * thus you cannot yield from #initialize which is called by mrb_funcall().
372 fiber_yield(mrb_state *mrb, mrb_value self)
377 mrb_get_args(mrb, "*!", &a, &len);
378 return mrb_fiber_yield(mrb, len, a);
383 * Fiber.current() -> fiber
385 * Returns the current fiber. If you are not running in the context of
386 * a fiber this method will return the root fiber.
389 fiber_current(mrb_state *mrb, mrb_value self)
392 struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
397 return mrb_obj_value(mrb->c->fib);
401 mrb_mruby_fiber_gem_init(mrb_state* mrb)
405 c = mrb_define_class(mrb, "Fiber", mrb->object_class);
406 MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
408 mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE());
409 mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY());
410 mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY());
411 mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE());
412 mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1));
414 mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
415 mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
417 mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
421 mrb_mruby_fiber_gem_final(mrb_state* mrb)