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
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |