[270] | 1 | #include "mruby.h"
|
---|
| 2 | #include "mruby/array.h"
|
---|
| 3 | #include "mruby/class.h"
|
---|
| 4 | #include "mruby/proc.h"
|
---|
| 5 |
|
---|
| 6 | #define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o))
|
---|
| 7 |
|
---|
| 8 | #define FIBER_STACK_INIT_SIZE 64
|
---|
| 9 | #define FIBER_CI_INIT_SIZE 8
|
---|
| 10 |
|
---|
| 11 | /*
|
---|
| 12 | * call-seq:
|
---|
| 13 | * Fiber.new{...} -> obj
|
---|
| 14 | *
|
---|
| 15 | * Creates a fiber, whose execution is suspend until it is explicitly
|
---|
| 16 | * resumed using <code>Fiber#resume</code> method.
|
---|
| 17 | * The code running inside the fiber can give up control by calling
|
---|
| 18 | * <code>Fiber.yield</code> in which case it yields control back to caller
|
---|
| 19 | * (the caller of the <code>Fiber#resume</code>).
|
---|
| 20 | *
|
---|
| 21 | * Upon yielding or termination the Fiber returns the value of the last
|
---|
| 22 | * executed expression
|
---|
| 23 | *
|
---|
| 24 | * For instance:
|
---|
| 25 | *
|
---|
| 26 | * fiber = Fiber.new do
|
---|
| 27 | * Fiber.yield 1
|
---|
| 28 | * 2
|
---|
| 29 | * end
|
---|
| 30 | *
|
---|
| 31 | * puts fiber.resume
|
---|
| 32 | * puts fiber.resume
|
---|
| 33 | * puts fiber.resume
|
---|
| 34 | *
|
---|
| 35 | * <em>produces</em>
|
---|
| 36 | *
|
---|
| 37 | * 1
|
---|
| 38 | * 2
|
---|
| 39 | * resuming dead fiber (FiberError)
|
---|
| 40 | *
|
---|
| 41 | * The <code>Fiber#resume</code> method accepts an arbitrary number of
|
---|
| 42 | * parameters, if it is the first call to <code>resume</code> then they
|
---|
| 43 | * will be passed as block arguments. Otherwise they will be the return
|
---|
| 44 | * value of the call to <code>Fiber.yield</code>
|
---|
| 45 | *
|
---|
| 46 | * Example:
|
---|
| 47 | *
|
---|
| 48 | * fiber = Fiber.new do |first|
|
---|
| 49 | * second = Fiber.yield first + 2
|
---|
| 50 | * end
|
---|
| 51 | *
|
---|
| 52 | * puts fiber.resume 10
|
---|
| 53 | * puts fiber.resume 14
|
---|
| 54 | * puts fiber.resume 18
|
---|
| 55 | *
|
---|
| 56 | * <em>produces</em>
|
---|
| 57 | *
|
---|
| 58 | * 12
|
---|
| 59 | * 14
|
---|
| 60 | * resuming dead fiber (FiberError)
|
---|
| 61 | *
|
---|
| 62 | */
|
---|
| 63 | static mrb_value
|
---|
| 64 | fiber_init(mrb_state *mrb, mrb_value self)
|
---|
| 65 | {
|
---|
| 66 | static const struct mrb_context mrb_context_zero = { 0 };
|
---|
| 67 | struct RFiber *f = fiber_ptr(self);
|
---|
| 68 | struct mrb_context *c;
|
---|
| 69 | struct RProc *p;
|
---|
| 70 | mrb_callinfo *ci;
|
---|
| 71 | mrb_value blk;
|
---|
| 72 | size_t slen;
|
---|
| 73 |
|
---|
| 74 | mrb_get_args(mrb, "&", &blk);
|
---|
| 75 |
|
---|
| 76 | if (mrb_nil_p(blk)) {
|
---|
| 77 | mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Fiber object without a block");
|
---|
| 78 | }
|
---|
| 79 | p = mrb_proc_ptr(blk);
|
---|
| 80 | if (MRB_PROC_CFUNC_P(p)) {
|
---|
| 81 | mrb_raise(mrb, E_FIBER_ERROR, "tried to create Fiber from C defined method");
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | f->cxt = (struct mrb_context*)mrb_malloc(mrb, sizeof(struct mrb_context));
|
---|
| 85 | *f->cxt = mrb_context_zero;
|
---|
| 86 | c = f->cxt;
|
---|
| 87 |
|
---|
| 88 | /* initialize VM stack */
|
---|
| 89 | slen = FIBER_STACK_INIT_SIZE;
|
---|
| 90 | if (p->body.irep->nregs > slen) {
|
---|
| 91 | slen += p->body.irep->nregs;
|
---|
| 92 | }
|
---|
| 93 | c->stbase = (mrb_value *)mrb_malloc(mrb, slen*sizeof(mrb_value));
|
---|
| 94 | c->stend = c->stbase + slen;
|
---|
| 95 | c->stack = c->stbase;
|
---|
| 96 |
|
---|
| 97 | #ifdef MRB_NAN_BOXING
|
---|
| 98 | {
|
---|
| 99 | mrb_value *p = c->stbase;
|
---|
| 100 | mrb_value *pend = c->stend;
|
---|
| 101 |
|
---|
| 102 | while (p < pend) {
|
---|
| 103 | SET_NIL_VALUE(*p);
|
---|
| 104 | p++;
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 | #else
|
---|
| 108 | memset(c->stbase, 0, slen * sizeof(mrb_value));
|
---|
| 109 | #endif
|
---|
| 110 |
|
---|
| 111 | /* copy receiver from a block */
|
---|
| 112 | c->stack[0] = mrb->c->stack[0];
|
---|
| 113 |
|
---|
| 114 | /* initialize callinfo stack */
|
---|
| 115 | c->cibase = (mrb_callinfo *)mrb_calloc(mrb, FIBER_CI_INIT_SIZE, sizeof(mrb_callinfo));
|
---|
| 116 | c->ciend = c->cibase + FIBER_CI_INIT_SIZE;
|
---|
| 117 | c->ci = c->cibase;
|
---|
| 118 | c->ci->stackent = c->stack;
|
---|
| 119 |
|
---|
| 120 | /* adjust return callinfo */
|
---|
| 121 | ci = c->ci;
|
---|
| 122 | ci->target_class = p->target_class;
|
---|
| 123 | ci->proc = p;
|
---|
| 124 | ci->pc = p->body.irep->iseq;
|
---|
| 125 | ci->nregs = p->body.irep->nregs;
|
---|
| 126 | ci[1] = ci[0];
|
---|
| 127 | c->ci++; /* push dummy callinfo */
|
---|
| 128 |
|
---|
| 129 | c->fib = f;
|
---|
| 130 | c->status = MRB_FIBER_CREATED;
|
---|
| 131 |
|
---|
| 132 | return self;
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | static struct mrb_context*
|
---|
| 136 | fiber_check(mrb_state *mrb, mrb_value fib)
|
---|
| 137 | {
|
---|
| 138 | struct RFiber *f = fiber_ptr(fib);
|
---|
| 139 |
|
---|
| 140 | mrb_assert(f->tt == MRB_TT_FIBER);
|
---|
| 141 | if (!f->cxt) {
|
---|
| 142 | mrb_raise(mrb, E_FIBER_ERROR, "uninitialized Fiber");
|
---|
| 143 | }
|
---|
| 144 | return f->cxt;
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | static mrb_value
|
---|
| 148 | fiber_result(mrb_state *mrb, const mrb_value *a, mrb_int len)
|
---|
| 149 | {
|
---|
| 150 | if (len == 0) return mrb_nil_value();
|
---|
| 151 | if (len == 1) return a[0];
|
---|
| 152 | return mrb_ary_new_from_values(mrb, len, a);
|
---|
| 153 | }
|
---|
| 154 |
|
---|
| 155 | /* mark return from context modifying method */
|
---|
| 156 | #define MARK_CONTEXT_MODIFY(c) (c)->ci->target_class = NULL
|
---|
| 157 |
|
---|
| 158 | static mrb_value
|
---|
| 159 | fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mrb_bool resume)
|
---|
| 160 | {
|
---|
| 161 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 162 | mrb_callinfo *ci;
|
---|
| 163 |
|
---|
| 164 | for (ci = c->ci; ci >= c->cibase; ci--) {
|
---|
| 165 | if (ci->acc < 0) {
|
---|
| 166 | mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
|
---|
| 167 | }
|
---|
| 168 | }
|
---|
| 169 | if (resume && c->status == MRB_FIBER_TRANSFERRED) {
|
---|
| 170 | mrb_raise(mrb, E_FIBER_ERROR, "resuming transferred fiber");
|
---|
| 171 | }
|
---|
| 172 | if (c->status == MRB_FIBER_RUNNING || c->status == MRB_FIBER_RESUMING) {
|
---|
| 173 | mrb_raise(mrb, E_FIBER_ERROR, "double resume");
|
---|
| 174 | }
|
---|
| 175 | if (c->status == MRB_FIBER_TERMINATED) {
|
---|
| 176 | mrb_raise(mrb, E_FIBER_ERROR, "resuming dead fiber");
|
---|
| 177 | }
|
---|
| 178 | mrb->c->status = resume ? MRB_FIBER_RESUMING : MRB_FIBER_TRANSFERRED;
|
---|
| 179 | c->prev = resume ? mrb->c : (c->prev ? c->prev : mrb->root_c);
|
---|
| 180 | if (c->status == MRB_FIBER_CREATED) {
|
---|
| 181 | mrb_value *b = c->stack+1;
|
---|
| 182 | mrb_value *e = b + len;
|
---|
| 183 |
|
---|
| 184 | while (b<e) {
|
---|
| 185 | *b++ = *a++;
|
---|
| 186 | }
|
---|
| 187 | c->cibase->argc = len;
|
---|
| 188 | if (c->prev->fib)
|
---|
| 189 | mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib);
|
---|
| 190 | mrb_write_barrier(mrb, (struct RBasic*)c->fib);
|
---|
| 191 | c->status = MRB_FIBER_RUNNING;
|
---|
| 192 | mrb->c = c;
|
---|
| 193 |
|
---|
| 194 | MARK_CONTEXT_MODIFY(c);
|
---|
| 195 | return c->ci->proc->env->stack[0];
|
---|
| 196 | }
|
---|
| 197 | MARK_CONTEXT_MODIFY(c);
|
---|
| 198 | if (c->prev->fib)
|
---|
| 199 | mrb_field_write_barrier(mrb, (struct RBasic*)c->fib, (struct RBasic*)c->prev->fib);
|
---|
| 200 | mrb_write_barrier(mrb, (struct RBasic*)c->fib);
|
---|
| 201 | c->status = MRB_FIBER_RUNNING;
|
---|
| 202 | mrb->c = c;
|
---|
| 203 | return fiber_result(mrb, a, len);
|
---|
| 204 | }
|
---|
| 205 |
|
---|
| 206 | /*
|
---|
| 207 | * call-seq:
|
---|
| 208 | * fiber.resume(args, ...) -> obj
|
---|
| 209 | *
|
---|
| 210 | * Resumes the fiber from the point at which the last <code>Fiber.yield</code>
|
---|
| 211 | * was called, or starts running it if it is the first call to
|
---|
| 212 | * <code>resume</code>. Arguments passed to resume will be the value of
|
---|
| 213 | * the <code>Fiber.yield</code> expression or will be passed as block
|
---|
| 214 | * parameters to the fiber's block if this is the first <code>resume</code>.
|
---|
| 215 | *
|
---|
| 216 | * Alternatively, when resume is called it evaluates to the arguments passed
|
---|
| 217 | * to the next <code>Fiber.yield</code> statement inside the fiber's block
|
---|
| 218 | * or to the block value if it runs to completion without any
|
---|
| 219 | * <code>Fiber.yield</code>
|
---|
| 220 | */
|
---|
| 221 | static mrb_value
|
---|
| 222 | fiber_resume(mrb_state *mrb, mrb_value self)
|
---|
| 223 | {
|
---|
| 224 | mrb_value *a;
|
---|
| 225 | mrb_int len;
|
---|
| 226 |
|
---|
| 227 | mrb_get_args(mrb, "*", &a, &len);
|
---|
| 228 | return fiber_switch(mrb, self, len, a, TRUE);
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | /* resume thread with given arguments */
|
---|
| 232 | MRB_API mrb_value
|
---|
| 233 | mrb_fiber_resume(mrb_state *mrb, mrb_value fib, mrb_int len, const mrb_value *a)
|
---|
| 234 | {
|
---|
| 235 | return fiber_switch(mrb, fib, len, a, TRUE);
|
---|
| 236 | }
|
---|
| 237 |
|
---|
| 238 | /*
|
---|
| 239 | * call-seq:
|
---|
| 240 | * fiber.alive? -> true or false
|
---|
| 241 | *
|
---|
| 242 | * Returns true if the fiber can still be resumed. After finishing
|
---|
| 243 | * execution of the fiber block this method will always return false.
|
---|
| 244 | */
|
---|
| 245 | static mrb_value
|
---|
| 246 | fiber_alive_p(mrb_state *mrb, mrb_value self)
|
---|
| 247 | {
|
---|
| 248 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 249 | return mrb_bool_value(c->status != MRB_FIBER_TERMINATED);
|
---|
| 250 | }
|
---|
| 251 |
|
---|
| 252 | static mrb_value
|
---|
| 253 | fiber_eq(mrb_state *mrb, mrb_value self)
|
---|
| 254 | {
|
---|
| 255 | mrb_value other;
|
---|
| 256 | mrb_get_args(mrb, "o", &other);
|
---|
| 257 |
|
---|
| 258 | if (mrb_type(other) != MRB_TT_FIBER) {
|
---|
| 259 | return mrb_false_value();
|
---|
| 260 | }
|
---|
| 261 | return mrb_bool_value(fiber_ptr(self) == fiber_ptr(other));
|
---|
| 262 | }
|
---|
| 263 |
|
---|
| 264 | /*
|
---|
| 265 | * call-seq:
|
---|
| 266 | * fiber.transfer(args, ...) -> obj
|
---|
| 267 | *
|
---|
| 268 | * Transfers control to receiver fiber of the method call.
|
---|
| 269 | * Unlike <code>resume</code> the receiver wouldn't be pushed to call
|
---|
| 270 | * stack of fibers. Instead it will switch to the call stack of
|
---|
| 271 | * transferring fiber.
|
---|
| 272 | * When resuming a fiber that was transferred to another fiber it would
|
---|
| 273 | * cause double resume error. Though when the fiber is re-transferred
|
---|
| 274 | * and <code>Fiber.yield</code> is called, the fiber would be resumable.
|
---|
| 275 | */
|
---|
| 276 | static mrb_value
|
---|
| 277 | fiber_transfer(mrb_state *mrb, mrb_value self)
|
---|
| 278 | {
|
---|
| 279 | struct mrb_context *c = fiber_check(mrb, self);
|
---|
| 280 | mrb_value* a;
|
---|
| 281 | mrb_int len;
|
---|
| 282 |
|
---|
| 283 | mrb_get_args(mrb, "*", &a, &len);
|
---|
| 284 |
|
---|
| 285 | if (c == mrb->root_c) {
|
---|
| 286 | mrb->c->status = MRB_FIBER_TRANSFERRED;
|
---|
| 287 | mrb->c = c;
|
---|
| 288 | c->status = MRB_FIBER_RUNNING;
|
---|
| 289 | MARK_CONTEXT_MODIFY(c);
|
---|
| 290 | mrb_write_barrier(mrb, (struct RBasic*)c->fib);
|
---|
| 291 | return fiber_result(mrb, a, len);
|
---|
| 292 | }
|
---|
| 293 |
|
---|
| 294 | if (c == mrb->c) {
|
---|
| 295 | return fiber_result(mrb, a, len);
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | return fiber_switch(mrb, self, len, a, FALSE);
|
---|
| 299 | }
|
---|
| 300 |
|
---|
| 301 | /* yield values to the caller fiber */
|
---|
| 302 | /* mrb_fiber_yield() must be called as `return mrb_fiber_yield(...)` */
|
---|
| 303 | MRB_API mrb_value
|
---|
| 304 | mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a)
|
---|
| 305 | {
|
---|
| 306 | struct mrb_context *c = mrb->c;
|
---|
| 307 | mrb_callinfo *ci;
|
---|
| 308 |
|
---|
| 309 | for (ci = c->ci; ci >= c->cibase; ci--) {
|
---|
| 310 | if (ci->acc < 0) {
|
---|
| 311 | mrb_raise(mrb, E_FIBER_ERROR, "can't cross C function boundary");
|
---|
| 312 | }
|
---|
| 313 | }
|
---|
| 314 | if (!c->prev) {
|
---|
| 315 | mrb_raise(mrb, E_FIBER_ERROR, "can't yield from root fiber");
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | c->prev->status = MRB_FIBER_RUNNING;
|
---|
| 319 | c->status = MRB_FIBER_SUSPENDED;
|
---|
| 320 | mrb->c = c->prev;
|
---|
| 321 | c->prev = NULL;
|
---|
| 322 | MARK_CONTEXT_MODIFY(mrb->c);
|
---|
| 323 | mrb_write_barrier(mrb, (struct RBasic*)c->fib);
|
---|
| 324 | return fiber_result(mrb, a, len);
|
---|
| 325 | }
|
---|
| 326 |
|
---|
| 327 | /*
|
---|
| 328 | * call-seq:
|
---|
| 329 | * Fiber.yield(args, ...) -> obj
|
---|
| 330 | *
|
---|
| 331 | * Yields control back to the context that resumed the fiber, passing
|
---|
| 332 | * along any arguments that were passed to it. The fiber will resume
|
---|
| 333 | * processing at this point when <code>resume</code> is called next.
|
---|
| 334 | * Any arguments passed to the next <code>resume</code> will be the
|
---|
| 335 | * value that this <code>Fiber.yield</code> expression evaluates to.
|
---|
| 336 | */
|
---|
| 337 | static mrb_value
|
---|
| 338 | fiber_yield(mrb_state *mrb, mrb_value self)
|
---|
| 339 | {
|
---|
| 340 | mrb_value *a;
|
---|
| 341 | mrb_int len;
|
---|
| 342 |
|
---|
| 343 | mrb_get_args(mrb, "*", &a, &len);
|
---|
| 344 | return mrb_fiber_yield(mrb, len, a);
|
---|
| 345 | }
|
---|
| 346 |
|
---|
| 347 | /*
|
---|
| 348 | * call-seq:
|
---|
| 349 | * Fiber.current() -> fiber
|
---|
| 350 | *
|
---|
| 351 | * Returns the current fiber. If you are not running in the context of
|
---|
| 352 | * a fiber this method will return the root fiber.
|
---|
| 353 | */
|
---|
| 354 | static mrb_value
|
---|
| 355 | fiber_current(mrb_state *mrb, mrb_value self)
|
---|
| 356 | {
|
---|
| 357 | if (!mrb->c->fib) {
|
---|
| 358 | struct RFiber *f = (struct RFiber*)mrb_obj_alloc(mrb, MRB_TT_FIBER, mrb_class_ptr(self));
|
---|
| 359 |
|
---|
| 360 | f->cxt = mrb->c;
|
---|
| 361 | mrb->c->fib = f;
|
---|
| 362 | }
|
---|
| 363 | return mrb_obj_value(mrb->c->fib);
|
---|
| 364 | }
|
---|
| 365 |
|
---|
| 366 | void
|
---|
| 367 | mrb_mruby_fiber_gem_init(mrb_state* mrb)
|
---|
| 368 | {
|
---|
| 369 | struct RClass *c;
|
---|
| 370 |
|
---|
| 371 | c = mrb_define_class(mrb, "Fiber", mrb->object_class);
|
---|
| 372 | MRB_SET_INSTANCE_TT(c, MRB_TT_FIBER);
|
---|
| 373 |
|
---|
| 374 | mrb_define_method(mrb, c, "initialize", fiber_init, MRB_ARGS_NONE());
|
---|
| 375 | mrb_define_method(mrb, c, "resume", fiber_resume, MRB_ARGS_ANY());
|
---|
| 376 | mrb_define_method(mrb, c, "transfer", fiber_transfer, MRB_ARGS_ANY());
|
---|
| 377 | mrb_define_method(mrb, c, "alive?", fiber_alive_p, MRB_ARGS_NONE());
|
---|
| 378 | mrb_define_method(mrb, c, "==", fiber_eq, MRB_ARGS_REQ(1));
|
---|
| 379 |
|
---|
| 380 | mrb_define_class_method(mrb, c, "yield", fiber_yield, MRB_ARGS_ANY());
|
---|
| 381 | mrb_define_class_method(mrb, c, "current", fiber_current, MRB_ARGS_NONE());
|
---|
| 382 |
|
---|
| 383 | mrb_define_class(mrb, "FiberError", mrb->eStandardError_class);
|
---|
| 384 | }
|
---|
| 385 |
|
---|
| 386 | void
|
---|
| 387 | mrb_mruby_fiber_gem_final(mrb_state* mrb)
|
---|
| 388 | {
|
---|
| 389 | }
|
---|