quickjs再谈libuv

一、前言

  这次通过这两篇博客,对libuv再次深入了解。因为后面结合iotjs,txiki.js会有很多地方用到libuv库。同时由于上一篇libuv博文是跟lvgl结合的。这一篇则比较纯粹的见解libuv库的使用。

二、默认的setTimeout例子,了解原理

1 import {setTimeout} from 'os'
2 
3 setTimeout(function() {
4   console.log('1000')
5 }, 1000)
6 
7 setTimeout(function() {
8   console.log('500')
9 }, 500)

  上面这个例子能正常的运行,并按照timeout时间,进行打印。但是仔细看quickjs-libc.c:2061 js_os_setTimeout 函数的实现,却是同步的代码,是如何实现异步执行的呢?

  是主进程中最后的 js_std_loop函数,实现类型select/poll效果(IO多路复用 https://www.cnblogs.com/wunaozai/p/3870338.html)

  大概流程如下:

1. 调用setTimeout函数时,将定时器加入到os_timers中 list_add_tail(&th->link, &ts->os_timers);
2. 执行loop主循环 void js_std_loop(JSContext *ctx)
3. IO多路复用代码循环 os_poll_func = js_os_poll;
4. 实际是这个函数 static int js_os_poll(JSContext *ctx)
5. 遍历所有的定时器 list_for_each(el, &ts->os_timers)
6. 执行定时器绑定的回调函数 call_handler(ctx, func);

三、移植到libuv

  了解了上面的原理后,使用libuv进行移植。默认的setTimeout也可以实现效果啊,为什么需要移植libuv来重写呢?之所以需要,之前的博客有提到,这个是内置的事件循环,如果还要集成其他库,如之前提到的lvgl-gui库,http异步调用等库时,quickjs默认的方式就不通用。需要改造。

  libuv三个概念,Callback,事件触发时执行的回调。Handler,控制器uv_timer_t类型定时器。Loop,异步IO循环,uv_loop_t类型。三者合在一起就是,将Callback绑定到Handler中,然后在Loop循环中,执行Handler.

  宏任务、微任务

  1 #include <stdio.h>
  2 #include "quickjs-libc.h"
  3 #include "quickjs.h"
  4 #include "cutils.h"
  5 #include "uv.h"
  6 
  7 uv_loop_t *main_loop;
  8 uv_loop_t *js_loop;
  9 static uv_loop_t js_loop_struct;
 10 typedef struct _uv_timer_data
 11 {
 12   JSContext *ctx;
 13   uv_timer_t handle;
 14   int interval;
 15   JSValue func;
 16   int argc;
 17   JSValue *argv;
 18 } uv_timer_data_t;
 19 
 20 static JSClassID tjs_timer_class_id;
 21 static void uv__timer_close(uv_handle_t *handle)
 22 {
 23   uv_timer_data_t *data = handle->data;
 24   free(data);
 25 }
 26 static void clear_timer(uv_timer_data_t *data)
 27 {
 28   JSContext *ctx = data->ctx;
 29   JS_FreeValue(ctx, data->func);
 30   data->func = JS_UNDEFINED;
 31   for (int i = 0; i < data->argc; i++)
 32   {
 33     JS_FreeValue(ctx, data->argv[i]);
 34     data->argv[i] = JS_UNDEFINED;
 35   }
 36   data->argc = 0;
 37 }
 38 static void tjs_timer_finalizer(JSRuntime *rt, JSValue val)
 39 {
 40   printf("tjs_timer_finalizer\n");
 41   uv_timer_data_t *data = (uv_timer_data_t *)JS_GetOpaque(val, tjs_timer_class_id);
 42   if (data)
 43   {
 44     clear_timer(data);
 45     uv_close((uv_handle_t *)&data->handle, uv__timer_close);
 46   }
 47 }
 48 
 49 static void tjs_timer_gc_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
 50 {
 51   uv_timer_data_t *data = (uv_timer_data_t *)JS_GetOpaque(val, tjs_timer_class_id);
 52   if (data)
 53   {
 54     JS_MarkValue(rt, data->func, mark_func);
 55     for (int i = 0; i < data->argc; i++)
 56       JS_MarkValue(rt, data->argv[i], mark_func);
 57   }
 58 }
 59 static JSClassDef tjs_timer_class = {
 60     "Timer",
 61     // .finalizer = tjs_timer_finalizer,
 62     .gc_mark = tjs_timer_gc_mark,
 63 };
 64 
 65 static void js_uv_setTimeout_cb(uv_timer_t *handle)
 66 {
 67   printf("setTimeout callback %p\n", handle);
 68   uv_timer_data_t *data = (uv_timer_data_t *)handle->data;
 69   JSContext *ctx = data->ctx;
 70   JSValue func = JS_DupValue(ctx, data->func);
 71   JSValue ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL);
 72   JS_FreeValue(ctx, func);
 73   if (JS_IsException(ret))
 74   {
 75     js_std_dump_error(ctx);
 76   }
 77   JS_FreeValue(ctx, ret);
 78   clear_timer(data);
 79   return;
 80 }
 81 static JSValue js_uv_setTimeout(JSContext *ctx, JSValueConst this_val,
 82                                 int argc, JSValueConst *argv)
 83 {
 84   printf("js_uv_setTimeout\n");
 85   int delay;
 86   JSValueConst func;
 87   func = argv[0];
 88   if (!JS_IsFunction(ctx, func))
 89   {
 90     printf("Not a function!\n");
 91     return JS_EXCEPTION;
 92   }
 93   JS_ToInt32(ctx, &delay, argv[1]);
 94 
 95   JSValue obj = JS_NewObjectClass(ctx, tjs_timer_class_id);
 96   if (JS_IsException(obj))
 97   {
 98     return JS_EXCEPTION;
 99   }
100 
101   uv_timer_data_t *data = calloc(1, sizeof(uv_timer_data_t));
102   data->ctx = ctx;
103   data->func = JS_DupValue(ctx, func);
104   data->handle.data = data;
105 
106   uv_timer_init(main_loop, &data->handle);
107   uv_timer_start(&data->handle, js_uv_setTimeout_cb, delay, 0);
108 
109   JS_SetOpaque(obj, data);
110   return obj;
111 }
112 static JSValue js_uv_clearTimeout(JSContext *ctx, JSValueConst this_val,
113                                   int argc, JSValueConst *argv)
114 {
115   uv_timer_data_t *data = (uv_timer_data_t *)JS_GetOpaque2(ctx, argv[0], tjs_timer_class_id);
116   if (!data)
117   {
118     return JS_EXCEPTION;
119   }
120   uv_timer_stop(&data->handle);
121   clear_timer(data);
122   return JS_UNDEFINED;
123 }
124 static const JSCFunctionListEntry js_uv_funcs[] = {
125     JS_CFUNC_DEF("setTimeout", 0, js_uv_setTimeout),
126     JS_CFUNC_DEF("clearTimeout", 1, js_uv_clearTimeout),
127 };
128 static int js_uv_init(JSContext *ctx, JSModuleDef *m)
129 {
130   JSRuntime *rt = JS_GetRuntime(ctx);
131   JS_NewClassID(&tjs_timer_class_id);
132   JS_NewClass(rt, tjs_timer_class_id, &tjs_timer_class);
133   return JS_SetModuleExportList(ctx, m, js_uv_funcs, countof(js_uv_funcs));
134 }
135 JSModuleDef *js_init_module_uv(JSContext *ctx, const char *module_name)
136 {
137   JSModuleDef *m = JS_NewCModule(ctx, module_name, js_uv_init);
138   if (!m)
139   {
140     return NULL;
141   }
142   JS_AddModuleExportList(ctx, m, js_uv_funcs, countof(js_uv_funcs));
143   return m;
144 }
145 
146 JSContext *js_init_context()
147 {
148   JSRuntime *rt = JS_NewRuntime();
149   js_std_init_handlers(rt);
150   JSContext *ctx = JS_NewContext(rt);
151   js_init_module_std(ctx, "std");
152   js_init_module_os(ctx, "os");
153   js_init_module_uv(ctx, "uv");
154   return ctx;
155 }
156 void run_js(JSContext *ctx)
157 {
158   const char *str =
159       "import * as std from 'std'\n"
160       "import * as uv from 'uv'\n"
161       "import * as os from 'os'\n"
162       "globalThis.std = std\n"
163       "globalThis.uv = uv\n"
164       "globalThis.os = os\n"
165       "var console = {};\n"
166       "console.log = function(msg) {\n"
167       "  std.printf(msg + '\\n');\n"
168       "}\n"
169       "globalThis.console = console;\n";
170   JSValue init_compile = JS_Eval(
171       ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
172   js_module_set_import_meta(ctx, init_compile, 1, 1);
173   JSValue init_run = JS_EvalFunction(ctx, init_compile);
174 
175   const char *buf =
176       "function test(){"
177       "console.log('hello');\n"
178       "var timer1 = uv.setTimeout(function(){console.log('uv timeout 1000');}, 1000);\n"
179       "uv.clearTimeout(timer1);\n"
180       "var f = () => {var timer2 = uv.setTimeout(function(){console.log('uv timeout 1001'); f(); }, 1001);}\n"
181       "f();\n"
182       "Promise.resolve().then(function() {\n"
183       "console.log('2000');\n"
184       "f();\n"
185       "})\n"
186       "console.log('end eval');\n"
187       "}\n"
188       "test();"
189       "console.log('end code');\n"
190       ;
191   JSValue result = JS_Eval(ctx, buf, strlen(buf), "test", JS_EVAL_TYPE_MODULE);
192   js_std_loop(ctx);
193   int clen;
194   if (JS_ToInt32(ctx, &clen, result) != 0)
195   {
196     js_std_dump_error(ctx);
197   }
198   printf("exit run_js\n");
199 }
200 
201 void on_walk_cb(uv_handle_t* handle, void* arg) {
202   printf("====================> on_walk_cb %p\n", handle);
203   uv_timer_data_t *data = (uv_timer_data_t *)handle->data;
204   clear_timer(data);
205   if(!uv_is_closing(handle)){
206     uv_close(handle, uv__timer_close);
207   }
208 }
209 static void onTimerTick(uv_timer_t *handle)
210 {
211   JSContext *ctx = (JSContext *)handle->data;
212   printf("====================> timer tick\n");
213   uv_stop(main_loop);
214   int result = uv_loop_close(main_loop);
215   printf("result = %s %d\n", uv_err_name(result), result);
216   if(result == UV_EBUSY){
217     uv_walk(main_loop, on_walk_cb, NULL);
218     uv_run(main_loop, UV_RUN_DEFAULT);
219     printf("====================> timer tick end\n");
220   }
221 }
222 
223 int main()
224 {
225   JSContext *ctx = js_init_context();
226   main_loop = uv_default_loop();
227   uv_loop_init(&js_loop_struct);
228   js_loop = &js_loop_struct;
229 
230   uv_timer_t *timerHandle = calloc(1, sizeof(uv_timer_t));
231   timerHandle->data = ctx;
232   // 把 handle 绑到 loop 上
233   uv_timer_init(js_loop, timerHandle);
234   // 把 callback 绑到 handle 上,并启动 timer
235   uv_timer_start(timerHandle, onTimerTick, 3000, 0);
236   
237   run_js(ctx);
238   
239   // 启动event loop
240   int r_js = 1;
241   int r_ma = 1;
242   while(r_js || r_ma){
243     r_js = uv_run(js_loop, UV_RUN_NOWAIT);
244     r_ma = uv_run(main_loop, UV_RUN_NOWAIT);
245   }
246   JS_FreeContext(ctx);
247   JS_FreeRuntime(JS_GetRuntime(ctx));
248   printf("uv_run() done.\n");
249   return 0;
250 }

  我在编译.a库是启用了两个DUMP宏 

#define DUMP_LEAKS  1
#define DUMP_MEM

  运行结果

 1 D:\test\quickjs\examples\libuv>bin\main.exe
 2 hello
 3 js_uv_setTimeout
 4 js_uv_setTimeout
 5 end eval
 6 end code
 7 2000
 8 js_uv_setTimeout
 9 exit run_js
10 setTimeout callback 0000000000BF5478
11 uv timeout 1001
12 js_uv_setTimeout
13 setTimeout callback 0000000000BF5E18
14 uv timeout 1001
15 js_uv_setTimeout
16 setTimeout callback 0000000000BF5398
17 uv timeout 1001
18 js_uv_setTimeout
19 setTimeout callback 0000000000BF5558
20 uv timeout 1001
21 js_uv_setTimeout
22 ====================> timer tick
23 result = EBUSY -4082
24 ====================> on_walk_cb 0000000000BF52B8
25 ====================> on_walk_cb 0000000000BF5478
26 ====================> on_walk_cb 0000000000BF5E18
27 ====================> on_walk_cb 0000000000BF5398
28 ====================> on_walk_cb 0000000000BF5558
29 ====================> on_walk_cb 0000000000BF57F8
30 ====================> on_walk_cb 0000000000BF5EF8
31 ====================> timer tick end
32 QuickJS memory usage -- BigNum 2021-03-27 version, 64-bit, malloc limit: -1
33 
34   712 + 0   JSRuntime
35   488 + 0   JSContext
36    80 + 0   JSObject
37    40 + 0   JSString
38   136 + 0   JSFunctionBytecode
39 
40 JSObject classes
41 
42 NAME                    COUNT     SIZE
43 memory allocated          552    55038  (99.7 per block)
44 memory used               395    31082  (8 overhead, 60.6 average slack)
45 atoms                     302    22568  (74.7 per atom)
46 strings                     1       46  (46.0 per string)
47 uv_run() done.

四、代码讲解

  封装成一个类导出,这一点参考上一章节,主要是通过 JSClassID JSClassDef 来创建。
  封装数据成一个结构体 typedef struct _uv_timer_data {} uv_timer_data_t;
  常规的对函数进行绑定 setTimeout、clearTimeout

  main函数这里,主要是创建两个uv_loop。主线程用来跑run_js脚本,main_loop线程用来跑setTimeout的异步函数。js_loop用来定时触发关闭main_loop线程。模拟关闭js程序。这样就能实现,通过加载js脚本作为一个应用,然后又可以关闭这个js应用。整体代码还是比较简单理解的。最后的JS_FreeContext、JS_FreeRuntime是用来关闭js运行时,同时由于编译的时候启用了 DUMP_LEAKS 宏,可以很方便的测试,运行的js脚本有没有内存泄露。这是一个很不错的功能。

 1 int main()
 2 {
 3   JSContext *ctx = js_init_context();
 4   main_loop = uv_default_loop();
 5   uv_loop_init(&js_loop_struct);
 6   js_loop = &js_loop_struct;
 7 
 8   uv_timer_t *timerHandle = calloc(1, sizeof(uv_timer_t));
 9   timerHandle->data = ctx;
10   // 把 handle 绑到 loop 上
11   uv_timer_init(js_loop, timerHandle);
12   // 把 callback 绑到 handle 上,并启动 timer
13   uv_timer_start(timerHandle, onTimerTick, 3000, 0);
14   
15   run_js(ctx);
16   
17   // 启动event loop
18   int r_js = 1;
19   int r_ma = 1;
20   while(r_js || r_ma){
21     r_js = uv_run(js_loop, UV_RUN_NOWAIT);
22     r_ma = uv_run(main_loop, UV_RUN_NOWAIT);
23   }
24   JS_FreeContext(ctx);
25   JS_FreeRuntime(JS_GetRuntime(ctx));
26   printf("uv_run() done.\n");
27   return 0;
28 }

  下面这个代码,是通过js_loop线程,在3s后,关闭js主线程,实现关闭js应用。由于我的js线程,里面通过setTimeout嵌套,是永远不会退出的。因此需要这个来实现关闭js应用。又由于直接通过uv_stop是无法关闭main_loop线程的。通过查资料,是需要通过uv_walk+uv_run,然后通知handle的walk回调,然后调用uv_close主动关闭。由于通过uv_close关闭setTimeout的js_loop线程,会导致setTimeout的回调函数里的变量没有经过 js_uv_setTimeout_cb函数调用,并且通过JS_Freexxx释放内存,因此,需要在这里调用clear_timer释放内存,否则在main函数调用JS_FreeRuntime会提示内存泄露。

 1 void on_walk_cb(uv_handle_t* handle, void* arg) {
 2   printf("====================> on_walk_cb %p\n", handle);
 3   uv_timer_data_t *data = (uv_timer_data_t *)handle->data;
 4   clear_timer(data);
 5   if(!uv_is_closing(handle)){
 6     uv_close(handle, uv__timer_close);
 7   }
 8 }
 9 static void onTimerTick(uv_timer_t *handle)
10 {
11   JSContext *ctx = (JSContext *)handle->data;
12   printf("====================> timer tick\n");
13   uv_stop(main_loop);
14   int result = uv_loop_close(main_loop);
15   printf("result = %s %d\n", uv_err_name(result), result);
16   if(result == UV_EBUSY){
17     uv_walk(main_loop, on_walk_cb, NULL);
18     uv_run(main_loop, UV_RUN_DEFAULT);
19     printf("====================> timer tick end\n");
20   }
21 }

  下面的代码是,setTimeout、clearTimeout 如何通过libuv进行实现的。 js_uv_setTimeout:通过JS_NewObjectClass创建一个Timer类。并将回调函数,运行时,绑定libuv-handle等封装成结构体后,调用uv_timer_start。并在delay后执行。在delay后,通过JS_Call执行绑定的回调函数。并释放内存。

 1 static void js_uv_setTimeout_cb(uv_timer_t *handle)
 2 {
 3   printf("setTimeout callback %p\n", handle);
 4   uv_timer_data_t *data = (uv_timer_data_t *)handle->data;
 5   JSContext *ctx = data->ctx;
 6   JSValue func = JS_DupValue(ctx, data->func);
 7   JSValue ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL);
 8   JS_FreeValue(ctx, func);
 9   if (JS_IsException(ret))
10   {
11     js_std_dump_error(ctx);
12   }
13   JS_FreeValue(ctx, ret);
14   clear_timer(data);
15   return;
16 }
17 static JSValue js_uv_setTimeout(JSContext *ctx, JSValueConst this_val,
18                                 int argc, JSValueConst *argv)
19 {
20   printf("js_uv_setTimeout\n");
21   int delay;
22   JSValueConst func;
23   func = argv[0];
24   if (!JS_IsFunction(ctx, func))
25   {
26     printf("Not a function!\n");
27     return JS_EXCEPTION;
28   }
29   JS_ToInt32(ctx, &delay, argv[1]);
30 
31   JSValue obj = JS_NewObjectClass(ctx, tjs_timer_class_id);
32   if (JS_IsException(obj))
33   {
34     return JS_EXCEPTION;
35   }
36 
37   uv_timer_data_t *data = calloc(1, sizeof(uv_timer_data_t));
38   data->ctx = ctx;
39   data->func = JS_DupValue(ctx, func);
40   data->handle.data = data;
41 
42   uv_timer_init(main_loop, &data->handle);
43   uv_timer_start(&data->handle, js_uv_setTimeout_cb, delay, 0);
44 
45   JS_SetOpaque(obj, data);
46   return obj;
47 }
48 static JSValue js_uv_clearTimeout(JSContext *ctx, JSValueConst this_val,
49                                   int argc, JSValueConst *argv)
50 {
51   uv_timer_data_t *data = (uv_timer_data_t *)JS_GetOpaque2(ctx, argv[0], tjs_timer_class_id);
52   if (!data)
53   {
54     return JS_EXCEPTION;
55   }
56   uv_timer_stop(&data->handle);
57   clear_timer(data);
58   return JS_UNDEFINED;
59 }

  这里的.finalizer .gc_mark我是没有用到。 至于这个 .finalizer类析构函数 没有用到,是因为,通过setTimeout设置回调函数后,js应用执行完或者在函数里面用setTimeout,会因为这个Timer类在生命周期内会自动删除,并执行finalizer回调。但是我们这个setTimeout一般不因为在变量生命周期内就停止的。因此这里没有用到finalizer,后面做iotjs设计时,这个Timer可能不能用类来实现。只能用普通的函数导出方式,然后挂载到globalThis这种方式。这种方式的话,要执行clearTimeout就比较麻烦,需要有个id来关闭这个setTimeout。后面在考虑怎么解决。

1 static JSClassDef tjs_timer_class = {
2     "Timer",
3     // .finalizer = tjs_timer_finalizer,
4     .gc_mark = tjs_timer_gc_mark,
5 };

  下面代码部分,我格式化成js显示

 1 function test() {
 2   console.log('hello');
 3   var timer1 = uv.setTimeout(function () { console.log('uv timeout 1000'); }, 1000);
 4   uv.clearTimeout(timer1);
 5   var f = () => { var timer2 = uv.setTimeout(function () { console.log('uv timeout 1001'); f(); }, 1001); }
 6   f();
 7   Promise.resolve().then(function () {
 8     console.log('2000');
 9     f();
10   })
11   console.log('end eval');
12 }
13 test(); 
14 console.log('end code');

 

 

 

参考资料:

  https://juejin.cn/post/6844904054032695304

  https://juejin.cn/post/6844904054628302855

  https://juejin.cn/post/6844903553031634952 (这一篇讲解EventLoop很好)

  ES6 Promise 

  https://www.cnblogs.com/lvdabao/p/es6-promise-1.html

  https://www.cnblogs.com/lvdabao/p/5320705.html

  https://www.cnblogs.com/lvdabao/p/jquery-deferred.html

 

本文地址:https://www.cnblogs.com/wunaozai/p/17877323.html

系列目录:https://www.cnblogs.com/wunaozai/p/17853962.html

posted @ 2023-12-08 15:10  无脑仔的小明  阅读(180)  评论(0编辑  收藏  举报