Skip to content

Commit fce26ed

Browse files
author
Yifeng Wang
committed
feat: add runtime setTimeout support
1 parent 3db480d commit fce26ed

File tree

5 files changed

+361
-6
lines changed

5 files changed

+361
-6
lines changed

src/list.h

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Linux klist like system
3+
*
4+
* Copyright (c) 2016-2017 Fabrice Bellard
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
#ifndef LIST_H
25+
#define LIST_H
26+
27+
#ifndef NULL
28+
#include <stddef.h>
29+
#endif
30+
31+
struct list_head {
32+
struct list_head *prev;
33+
struct list_head *next;
34+
};
35+
36+
#define LIST_HEAD_INIT(el) { &(el), &(el) }
37+
38+
/* return the pointer of type 'type *' containing 'el' as field 'member' */
39+
#define list_entry(el, type, member) \
40+
((type *)((uint8_t *)(el) - offsetof(type, member)))
41+
42+
static inline void init_list_head(struct list_head *head)
43+
{
44+
head->prev = head;
45+
head->next = head;
46+
}
47+
48+
/* insert 'el' between 'prev' and 'next' */
49+
static inline void __list_add(struct list_head *el,
50+
struct list_head *prev, struct list_head *next)
51+
{
52+
prev->next = el;
53+
el->prev = prev;
54+
el->next = next;
55+
next->prev = el;
56+
}
57+
58+
/* add 'el' at the head of the list 'head' (= after element head) */
59+
static inline void list_add(struct list_head *el, struct list_head *head)
60+
{
61+
__list_add(el, head, head->next);
62+
}
63+
64+
/* add 'el' at the end of the list 'head' (= before element head) */
65+
static inline void list_add_tail(struct list_head *el, struct list_head *head)
66+
{
67+
__list_add(el, head->prev, head);
68+
}
69+
70+
static inline void list_del(struct list_head *el)
71+
{
72+
struct list_head *prev, *next;
73+
prev = el->prev;
74+
next = el->next;
75+
prev->next = next;
76+
next->prev = prev;
77+
el->prev = NULL; /* fail safe */
78+
el->next = NULL; /* fail safe */
79+
}
80+
81+
static inline int list_empty(struct list_head *el)
82+
{
83+
return el->next == el;
84+
}
85+
86+
#define list_for_each(el, head) \
87+
for(el = (head)->next; el != (head); el = el->next)
88+
89+
#define list_for_each_safe(el, el1, head) \
90+
for(el = (head)->next, el1 = el->next; el != (head); \
91+
el = el1, el1 = el->next)
92+
93+
#define list_for_each_prev(el, head) \
94+
for(el = (head)->prev; el != (head); el = el->prev)
95+
96+
#define list_for_each_prev_safe(el, el1, head) \
97+
for(el = (head)->prev, el1 = el->prev; el != (head); \
98+
el = el1, el1 = el->prev)
99+
100+
#endif /* LIST_H */

src/main.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#include <stdio.h>
22
#include "quickjs/quickjs-libc.h"
3+
#include "runtime.h"
34
#include "uv.h"
45

56
static void onTimerTick(uv_timer_t *handle) {
6-
printf("timer tick\n");
7+
printf("libuv timer tick\n");
78
}
89

910
static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
@@ -57,14 +58,17 @@ int main(int argc, char **argv)
5758
{
5859
extern JSModuleDef *js_init_module_fib(JSContext *ctx, const char *name);
5960
js_init_module_fib(ctx, "fib.so");
61+
62+
extern JSModuleDef *js_init_module_my_os(JSContext *ctx, const char *name);
63+
js_init_module_my_os(ctx, "os");
6064
}
6165

6266
uint8_t *buf;
6367
size_t buf_len;
6468
const char *filename = "../src/test.js";
6569
buf = js_load_file(ctx, &buf_len, filename);
6670
eval_buf(ctx, buf, buf_len, filename, JS_EVAL_TYPE_MODULE);
67-
// js_rt_loop(ctx);
71+
js_rt_loop(ctx);
6872

6973
JS_FreeContext(ctx);
7074
JS_FreeRuntime(rt);

src/runtime.c

Lines changed: 251 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,253 @@
1+
#include <time.h>
2+
#include <sys/time.h>
13

4+
#include "quickjs/quickjs-libc.h"
5+
#include "quickjs/quickjs.h"
6+
#include "cutils.h"
7+
#include "list.h"
28
#include "runtime.h"
39

10+
static uint64_t os_pending_signals;
11+
static int (*os_poll_func)(JSContext *ctx);
12+
13+
static JSClassID js_os_timer_class_id;
14+
15+
typedef struct {
16+
struct list_head link;
17+
int fd;
18+
JSValue rw_func[2];
19+
} JSOSRWHandler;
20+
21+
typedef struct {
22+
struct list_head link;
23+
int sig_num;
24+
JSValue func;
25+
} JSOSSignalHandler;
26+
27+
typedef struct {
28+
struct list_head link;
29+
BOOL has_object;
30+
int64_t timeout;
31+
JSValue func;
32+
} JSOSTimer;
33+
34+
static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers);
35+
static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers);
36+
static struct list_head os_timers = LIST_HEAD_INIT(os_timers);
37+
38+
static int64_t get_time_ms(void)
39+
{
40+
struct timespec ts;
41+
clock_gettime(CLOCK_MONOTONIC, &ts);
42+
return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000);
43+
}
44+
45+
static void unlink_timer(JSRuntime *rt, JSOSTimer *th)
46+
{
47+
if (th->link.prev) {
48+
list_del(&th->link);
49+
th->link.prev = th->link.next = NULL;
50+
}
51+
}
52+
53+
static void free_timer(JSRuntime *rt, JSOSTimer *th)
54+
{
55+
JS_FreeValueRT(rt, th->func);
56+
js_free_rt(rt, th);
57+
}
58+
59+
static void js_os_timer_finalizer(JSRuntime *rt, JSValue val)
60+
{
61+
JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
62+
if (th) {
63+
th->has_object = FALSE;
64+
if (!th->link.prev)
65+
free_timer(rt, th);
66+
}
67+
}
68+
69+
static void js_os_timer_mark(JSRuntime *rt, JSValueConst val,
70+
JS_MarkFunc *mark_func)
71+
{
72+
JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
73+
if (th) {
74+
JS_MarkValue(rt, th->func, mark_func);
75+
}
76+
}
77+
78+
static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val,
79+
int argc, JSValueConst *argv)
80+
{
81+
int64_t delay;
82+
JSValueConst func;
83+
JSOSTimer *th;
84+
JSValue obj;
85+
86+
func = argv[0];
87+
if (!JS_IsFunction(ctx, func))
88+
return JS_ThrowTypeError(ctx, "not a function");
89+
if (JS_ToInt64(ctx, &delay, argv[1]))
90+
return JS_EXCEPTION;
91+
obj = JS_NewObjectClass(ctx, js_os_timer_class_id);
92+
if (JS_IsException(obj))
93+
return obj;
94+
th = js_mallocz(ctx, sizeof(*th));
95+
if (!th) {
96+
JS_FreeValue(ctx, obj);
97+
return JS_EXCEPTION;
98+
}
99+
th->has_object = TRUE;
100+
th->timeout = get_time_ms() + delay;
101+
th->func = JS_DupValue(ctx, func);
102+
list_add_tail(&th->link, &os_timers);
103+
JS_SetOpaque(obj, th);
104+
return obj;
105+
}
106+
107+
static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val,
108+
int argc, JSValueConst *argv)
109+
{
110+
JSOSTimer *th = JS_GetOpaque2(ctx, argv[0], js_os_timer_class_id);
111+
if (!th)
112+
return JS_EXCEPTION;
113+
unlink_timer(JS_GetRuntime(ctx), th);
114+
return JS_UNDEFINED;
115+
}
116+
117+
static JSClassDef js_os_timer_class = {
118+
"OSTimer",
119+
.finalizer = js_os_timer_finalizer,
120+
.gc_mark = js_os_timer_mark,
121+
};
122+
123+
static void call_handler(JSContext *ctx, JSValueConst func)
124+
{
125+
JSValue ret, func1;
126+
/* 'func' might be destroyed when calling itself (if it frees the
127+
handler), so must take extra care */
128+
func1 = JS_DupValue(ctx, func);
129+
ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL);
130+
JS_FreeValue(ctx, func1);
131+
if (JS_IsException(ret))
132+
js_std_dump_error(ctx);
133+
JS_FreeValue(ctx, ret);
134+
}
135+
136+
static int js_os_poll(JSContext *ctx)
137+
{
138+
int ret, fd_max, min_delay;
139+
int64_t cur_time, delay;
140+
fd_set rfds, wfds;
141+
JSOSRWHandler *rh;
142+
struct list_head *el;
143+
struct timeval tv, *tvp;
144+
145+
if (unlikely(os_pending_signals != 0)) {
146+
JSOSSignalHandler *sh;
147+
uint64_t mask;
148+
149+
list_for_each(el, &os_signal_handlers) {
150+
sh = list_entry(el, JSOSSignalHandler, link);
151+
mask = (uint64_t)1 << sh->sig_num;
152+
if (os_pending_signals & mask) {
153+
os_pending_signals &= ~mask;
154+
call_handler(ctx, sh->func);
155+
return 0;
156+
}
157+
}
158+
}
159+
160+
if (list_empty(&os_rw_handlers) && list_empty(&os_timers))
161+
return -1; /* no more events */
162+
163+
if (!list_empty(&os_timers)) {
164+
cur_time = get_time_ms();
165+
min_delay = 10000;
166+
list_for_each(el, &os_timers) {
167+
JSOSTimer *th = list_entry(el, JSOSTimer, link);
168+
delay = th->timeout - cur_time;
169+
if (delay <= 0) {
170+
JSValue func;
171+
/* the timer expired */
172+
func = th->func;
173+
th->func = JS_UNDEFINED;
174+
unlink_timer(JS_GetRuntime(ctx), th);
175+
if (!th->has_object)
176+
free_timer(JS_GetRuntime(ctx), th);
177+
call_handler(ctx, func);
178+
JS_FreeValue(ctx, func);
179+
return 0;
180+
} else if (delay < min_delay) {
181+
min_delay = delay;
182+
}
183+
}
184+
tv.tv_sec = min_delay / 1000;
185+
tv.tv_usec = (min_delay % 1000) * 1000;
186+
tvp = &tv;
187+
} else {
188+
tvp = NULL;
189+
}
190+
191+
FD_ZERO(&rfds);
192+
FD_ZERO(&wfds);
193+
fd_max = -1;
194+
list_for_each(el, &os_rw_handlers) {
195+
rh = list_entry(el, JSOSRWHandler, link);
196+
fd_max = max_int(fd_max, rh->fd);
197+
if (!JS_IsNull(rh->rw_func[0]))
198+
FD_SET(rh->fd, &rfds);
199+
if (!JS_IsNull(rh->rw_func[1]))
200+
FD_SET(rh->fd, &wfds);
201+
}
202+
203+
ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
204+
if (ret > 0) {
205+
list_for_each(el, &os_rw_handlers) {
206+
rh = list_entry(el, JSOSRWHandler, link);
207+
if (!JS_IsNull(rh->rw_func[0]) &&
208+
FD_ISSET(rh->fd, &rfds)) {
209+
call_handler(ctx, rh->rw_func[0]);
210+
/* must stop because the list may have been modified */
211+
break;
212+
}
213+
if (!JS_IsNull(rh->rw_func[1])) {
214+
FD_SET(rh->fd, &wfds);
215+
call_handler(ctx, rh->rw_func[1]);
216+
/* must stop because the list may have been modified */
217+
break;
218+
}
219+
}
220+
}
221+
return 0;
222+
}
223+
224+
static const JSCFunctionListEntry js_os_funcs[] = {
225+
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
226+
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
227+
};
228+
229+
static int js_os_init(JSContext *ctx, JSModuleDef *m)
230+
{
231+
os_poll_func = js_os_poll;
232+
233+
/* OSTimer class */
234+
JS_NewClassID(&js_os_timer_class_id);
235+
JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class);
236+
237+
return JS_SetModuleExportList(ctx, m, js_os_funcs,
238+
countof(js_os_funcs));
239+
}
240+
241+
JSModuleDef *js_init_module_my_os(JSContext *ctx, const char *module_name)
242+
{
243+
JSModuleDef *m;
244+
m = JS_NewCModule(ctx, module_name, js_os_init);
245+
if (!m)
246+
return NULL;
247+
JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs));
248+
return m;
249+
}
250+
4251
void js_rt_loop(JSContext *ctx)
5252
{
6253
JSContext *ctx1;
@@ -16,5 +263,8 @@ void js_rt_loop(JSContext *ctx)
16263
break;
17264
}
18265
}
266+
267+
if (!os_poll_func || os_poll_func(ctx))
268+
break;
19269
}
20-
}
270+
}

0 commit comments

Comments
 (0)