Skip to content

Commit de8d7b5

Browse files
committed
provide setTimeout to use sleep by Ruby
1 parent 35416e1 commit de8d7b5

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

ext/quickjsrb/quickjsrb.c

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,37 @@ static JSValue js_quickjsrb_call_global(JSContext *ctx, JSValueConst _this, int
381381
}
382382
}
383383

384+
static JSValue js_delay_and_eval_job(JSContext *ctx, int argc, JSValueConst *argv)
385+
{
386+
VALUE rb_delay_msec = to_rb_value(ctx, argv[1]);
387+
VALUE rb_delay_sec = rb_funcall(rb_delay_msec, rb_intern("/"), 1, rb_float_new(1000));
388+
rb_thread_wait_for(rb_time_interval(rb_delay_sec));
389+
JS_Call(ctx, argv[0], JS_UNDEFINED, 0, NULL);
390+
391+
return JS_UNDEFINED;
392+
}
393+
394+
static JSValue js_quickjsrb_set_timeout(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv)
395+
{
396+
JSValueConst func;
397+
int64_t delay;
398+
399+
func = argv[0];
400+
if (!JS_IsFunction(ctx, func))
401+
return JS_ThrowTypeError(ctx, "not a function");
402+
if (JS_ToInt64(ctx, &delay, argv[1])) // TODO: should be lower than global timeout
403+
return JS_EXCEPTION;
404+
405+
JSValueConst args[2];
406+
args[0] = func;
407+
args[1] = argv[1]; // delay
408+
// TODO: implement timer manager and poll with quickjs' queue
409+
// Currently, queueing multiple js_delay_and_eval_job is not parallelized
410+
JS_EnqueueJob(ctx, js_delay_and_eval_job, 2, args);
411+
412+
return JS_UNDEFINED;
413+
}
414+
384415
static JSValue js_quickjsrb_log(JSContext *ctx, JSValueConst _this, int argc, JSValueConst *argv, const char *severity)
385416
{
386417
VMData *data = JS_GetContextOpaque(ctx);
@@ -492,6 +523,8 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
492523
JS_SetModuleLoaderFunc(runtime, NULL, js_module_loader, NULL);
493524
js_std_init_handlers(runtime);
494525

526+
JSValue j_global = JS_GetGlobalObject(data->context);
527+
495528
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featureStdId))))
496529
{
497530
js_init_module_std(data->context, "std");
@@ -524,6 +557,12 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
524557
free(enableTimeout);
525558
JS_FreeValue(data->context, j_timeoutEval);
526559
}
560+
else if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featureOsTimeoutBetaId))))
561+
{
562+
JS_SetPropertyStr(
563+
data->context, j_global, "setTimeout",
564+
JS_NewCFunction(data->context, js_quickjsrb_set_timeout, "setTimeout", 2));
565+
}
527566

528567
if (RTEST(rb_funcall(r_features, rb_intern("include?"), 1, QUICKJSRB_SYM(featurePolyfillIntlId))))
529568
{
@@ -553,7 +592,6 @@ static VALUE vm_m_initialize(int argc, VALUE *argv, VALUE r_self)
553592
data->context, j_console, "error",
554593
JS_NewCFunction(data->context, js_console_error, "error", 1));
555594

556-
JSValue j_global = JS_GetGlobalObject(data->context);
557595
JS_SetPropertyStr(data->context, j_global, "console", j_console);
558596
JS_FreeValue(data->context, j_global);
559597

ext/quickjsrb/quickjsrb.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern const uint8_t qjsc_polyfill_intl_en_min;
1818
const char *featureStdId = "feature_std";
1919
const char *featureOsId = "feature_os";
2020
const char *featureOsTimeoutId = "feature_os_timeout";
21+
const char *featureOsTimeoutBetaId = "feature_os_timeout_beta";
2122
const char *featurePolyfillIntlId = "feature_polyfill_intl";
2223

2324
const char *undefinedId = "undefined";
@@ -137,6 +138,7 @@ static void r_define_constants(VALUE r_parent_class)
137138
rb_define_const(r_parent_class, "MODULE_STD", QUICKJSRB_SYM(featureStdId));
138139
rb_define_const(r_parent_class, "MODULE_OS", QUICKJSRB_SYM(featureOsId));
139140
rb_define_const(r_parent_class, "FEATURES_TIMEOUT", QUICKJSRB_SYM(featureOsTimeoutId));
141+
rb_define_const(r_parent_class, "FEATURES_TIMEOUT_BETA", QUICKJSRB_SYM(featureOsTimeoutBetaId));
140142
rb_define_const(r_parent_class, "POLYFILL_INTL", QUICKJSRB_SYM(featurePolyfillIntlId));
141143

142144
VALUE rb_cQuickjsValue = rb_define_class_under(r_parent_class, "Value", rb_cObject);

test/blocking_test.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def refute_sleep_a_sec_within_thread(&block)
2121

2222
class ProcessBlocking < QuickjsBlockingTest
2323
setup do
24-
@vm = Quickjs::VM.new(timeout_msec: 1200, features: [::Quickjs::MODULE_OS])
24+
@vm = Quickjs::VM.new(timeout_msec: 500, features: [::Quickjs::MODULE_OS])
2525
end
2626
teardown { @vm = nil }
2727

@@ -69,4 +69,17 @@ class ProcessBlocking < QuickjsBlockingTest
6969
end
7070
end
7171
end
72+
73+
class RubyBasedTimeout < QuickjsBlockingTest
74+
setup do
75+
@vm = Quickjs::VM.new(timeout_msec: 500, features: [::Quickjs::FEATURES_TIMEOUT_BETA])
76+
end
77+
teardown { @vm = nil }
78+
79+
test 'awaiting setTimeout does not block other threads' do
80+
assert_sleep_a_sec_within_thread do
81+
@vm.eval_code('await new Promise(resolve => setTimeout(resolve, 100));')
82+
end
83+
end
84+
end
7285
end

0 commit comments

Comments
 (0)