Skip to content

Commit 32cbe2f

Browse files
kateinoigakukuntmtm
andcommitted
Prohibit await in non-async context and remove unnecessary loop fiber
Some part of this change are derived from #205 by @tmtm Co-authored-by: TOMITA Masahiro <[email protected]>
1 parent ac38a84 commit 32cbe2f

File tree

3 files changed

+21
-32
lines changed

3 files changed

+21
-32
lines changed

ext/js/lib/js.rb

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,42 +39,22 @@ module JS
3939
Null = JS.eval("return null")
4040

4141
class PromiseScheduler
42-
Task = Struct.new(:fiber, :status, :value)
43-
44-
def initialize(main_fiber)
45-
@tasks = []
46-
@is_spinning = false
47-
@loop_fiber =
48-
Fiber.new do
49-
loop do
50-
while task = @tasks.shift
51-
task.fiber.transfer(task.value, task.status)
52-
end
53-
@is_spinning = false
54-
main_fiber.transfer
55-
end
56-
end
42+
def initialize(loop)
43+
@loop = loop
5744
end
5845

5946
def await(promise)
6047
current = Fiber.current
6148
promise.call(
6249
:then,
63-
->(value) { enqueue Task.new(current, :success, value) },
64-
->(value) { enqueue Task.new(current, :failure, value) }
50+
->(value) { current.transfer(value, :success) },
51+
->(value) { current.transfer(value, :failure) }
6552
)
66-
value, status = @loop_fiber.transfer
53+
raise "JS::Object#await can be called only from evalAsync" if @loop == current
54+
value, status = @loop.transfer
6755
raise JS::Error.new(value) if status == :failure
6856
value
6957
end
70-
71-
def enqueue(task)
72-
@tasks << task
73-
unless @is_spinning
74-
@is_spinning = true
75-
JS.global.queueMicrotask -> { @loop_fiber.transfer }
76-
end
77-
end
7858
end
7959

8060
@promise_scheduler = PromiseScheduler.new Fiber.current
@@ -120,8 +100,8 @@ def respond_to_missing?(sym, include_private)
120100
# This method looks like a synchronous method, but it actually runs asynchronously using fibers.
121101
# In other words, the next line to the `await` call at Ruby source will be executed after the
122102
# promise will be resolved. However, it does not block JavaScript event loop, so the next line
123-
# to the `RubyVM.eval` or `RubyVM.evalAsync` (in the case when no `await` operator before the
124-
# call expression) at JavaScript source will be executed without waiting for the promise.
103+
# to the RubyVM.evalAsync` (in the case when no `await` operator before the call expression)
104+
# at JavaScript source will be executed without waiting for the promise.
125105
#
126106
# The below example shows how the execution order goes. It goes in the order of "step N"
127107
#

packages/npm-packages/ruby-wasm-wasi/test/eval_async.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ describe("Async Ruby code evaluation", () => {
3232

3333
test("await outside of evalAsync", async () => {
3434
const vm = await initRubyVM();
35-
const result = vm.eval(
36-
`require "js"; JS.global[:Promise].resolve(42).await`
37-
);
38-
expect(result.call("nil?").toString()).toBe("true");
35+
expect(() => {
36+
vm.eval(`require "js"; JS.global[:Promise].resolve(42).await`);
37+
}).toThrowError("JS::Object#await can be called only from evalAsync");
3938
});
4039
});

packages/npm-packages/ruby-wasm-wasi/test/unit/test_async.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,14 @@ def test_concurrent_promises
4242
assert_equal 43, pac1[:promise].await.to_i
4343
assert_equal 42, pac0[:promise].await.to_i
4444
end
45+
46+
def test_await_in_fiber
47+
fiber_ended = false
48+
Fiber.new do
49+
promise = JS.eval("return Promise.resolve(42)")
50+
assert_equal 42, promise.await.to_i
51+
fiber_ended = true
52+
end.resume
53+
assert_equal true, fiber_ended
54+
end
4555
end

0 commit comments

Comments
 (0)