Procs passed to Fibers via C throw "double resume" error if `Fiber.yield` is called from a loop
Closed this issue · 2 comments
I am currently working on a project, where I try to embed mruby fibers into another programming language.
While doing so, I encountered a weird phenomenon (here broken down to a simple C example).
The following code is supposed to copy a Proc as argument to a new Fiber object and then execute the Fiber a few times.
C source file:
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/compile.h>
int main() {
mrb_state *mrb = mrb_open();
FILE* file = fopen("script.rb", "r");
mrb_load_file(mrb, file);
if(file) fclose(file);
mrb_value proc_value = mrb_load_string(mrb, "$proc");
mrb_value test_class = mrb_obj_value(mrb_class_get(mrb, "Test"));
mrb_sym method_sym = mrb_intern(mrb, "fiber_from", 10);
mrb_value fiber_value = mrb_funcall_argv(mrb, test_class, method_sym, 1, &proc_value);
for(int i = 0; i < 5; i++) {
mrb_sym resume_method_sym = mrb_intern(mrb, "resume", 6);
// Relevant function call is here
mrb_funcall_argv(mrb, fiber_value, resume_method_sym, 0, NULL);
if(mrb->exc) mrb_print_error(mrb);
}
mrb_close(mrb);
return 0;
}
Ruby script file:
# script.rb
class Test
def self.fiber_from(proc)
Fiber.new(&proc)
end
end
$proc = Proc.new do
0.upto(5) do |i|
puts i
Fiber.yield
puts 10 + i
end
end
When compiling and executing the C file, I get the following results:
0
10
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: double resume (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: double resume (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: double resume (FiberError)
However, if I replace the Proc constructor argument with the following code, everything works fine:
$proc = Proc.new do
puts 1
Fiber.yield
puts 2
Fiber.yield
puts 3
Fiber.yield
puts 4
Fiber.yield
puts 5
end
Result:
1
2
3
4
5
Other loop instructions like loop
and while
show the same problem, so I'd guess that the issue lies somewhere with the loop block. Also the problem does not occur until the end of the loop block is reached for the first time.
The issue does also not arise when I call Test.fiber_from
and the following resume
calls from the script directly, so the detour over C seems to be relevant here.
If I'm missing or misunderstanding some essential things here, please let me know.
Further information:
- mruby version: 3.1.0
- Platform: Windows 10 64bit
- Compiler: Visual Studio 2019
The latter was working rather accidentally. In fact, the latest version gives you the following errors for both version of script.rb
.
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: can't cross C function boundary (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: can't cross C function boundary (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: can't cross C function boundary (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: can't cross C function boundary (FiberError)
trace (most recent call last):
[1] (unknown):0
(unknown):0:in resume: can't cross C function boundary (FiberError)
The point is C function boundary. Since mruby fibers are only swap VM stacks (and not C stack), so when resume
is called from different C stack position, it raises an error. You used mrb_funcall
to call resume
but since mrb_funcall
itself is a C function, you got function boundary
error.
To fix this, use API to resume the fiber, replace
mrb_funcall_argv(mrb, fiber_value, resume_method_sym, 0, NULL);
by
mrb_fiber_resume(mrb, fiber_value, 0, NULL);
I believe it would work (at least this simplified version worked).
Thank you very much, that did indeed solve it!