aboutsummaryrefslogtreecommitdiff
path: root/nginx/ngx_js.c
diff options
context:
space:
mode:
authorDmitry Volyntsev <xeioex@nginx.com>2024-06-14 20:54:28 -0700
committerDmitry Volyntsev <xeioexception@gmail.com>2024-09-17 18:05:57 -0700
commit201b127679e27fe63eff5c1b4356ec4ed9ec4611 (patch)
tree2477939f07f646ee6137a7d30ccc8e807206da61 /nginx/ngx_js.c
parent9b6744129ce4c18e74b5e41cd1da66f2cbadcf36 (diff)
downloadnjs-201b127679e27fe63eff5c1b4356ec4ed9ec4611.tar.gz
njs-201b127679e27fe63eff5c1b4356ec4ed9ec4611.zip
Modules: introduced QuickJS engine.
"js_engine" directive is introduced which sets JavaScript engine. When the module is built with QuickJS library "js_engine qjs;" sets QuickJS engine for the current block. By default njs engine is used. For example, nginx.conf: location /a { js_engine qjs; # will be handled by QuickJS js_content main.handler; } location /b { # will be handled by njs js_content main.handler; } QuickJS engine implements drop-in replacement for nginx/njs objects with the following exceptions: * nginx module API to be added later: ngx.fetch(), ngx.shared.dict. * Built-in modules to be added later: fs, crypto, WebCrypto, xml. * NJS specific API: njs.dump(), njs.on(), console.dump(). * js_preload_object directive.
Diffstat (limited to 'nginx/ngx_js.c')
-rw-r--r--nginx/ngx_js.c1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
index ce4988f9..5fe3dc84 100644
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -8,6 +8,7 @@
#include <ngx_config.h>
#include <ngx_core.h>
+#include <math.h>
#include "ngx_js.h"
@@ -57,6 +58,53 @@ static ngx_int_t ngx_engine_njs_string(ngx_engine_t *e,
static void ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
ngx_js_loc_conf_t *conf);
+#if (NJS_HAVE_QUICKJS)
+static ngx_int_t ngx_engine_qjs_init(ngx_engine_t *engine,
+ ngx_engine_opts_t *opts);
+static ngx_int_t ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log,
+ u_char *start, size_t size);
+static ngx_int_t ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
+ njs_opaque_value_t *args, njs_uint_t nargs);
+static void *ngx_engine_qjs_external(ngx_engine_t *engine);
+static ngx_int_t ngx_engine_qjs_pending(ngx_engine_t *engine);
+static ngx_int_t ngx_engine_qjs_string(ngx_engine_t *e,
+ njs_opaque_value_t *value, ngx_str_t *str);
+
+static JSValue ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int immediate);
+static JSValue ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+
+static JSValue ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_constant_integer(JSContext *cx,
+ JSValueConst this_val, int magic);
+static JSValue ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv, int level);
+static JSValue ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv);
+static JSValue ngx_qjs_ext_console_time_end(JSContext *cx,
+ JSValueConst this_val, int argc, JSValueConst *argv);
+static JSValue ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val);
+static JSValue ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val);
+
+static void ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val);
+
+static JSModuleDef *ngx_qjs_module_loader(JSContext *ctx,
+ const char *module_name, void *opaque);
+static int ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx);
+static void ngx_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise,
+ JSValueConst reason, JS_BOOL is_handled, void *opaque);
+
+static JSValue ngx_qjs_value(JSContext *cx, const ngx_str_t *path);
+static ngx_int_t ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val,
+ ngx_str_t *dst);
+
+static JSModuleDef *ngx_qjs_core_init(JSContext *cx, const char *name);
+#endif
+
static njs_int_t ngx_js_ext_build(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
static njs_int_t ngx_js_ext_conf_file_path(njs_vm_t *vm,
@@ -377,6 +425,57 @@ njs_module_t *njs_js_addon_modules_shared[] = {
static njs_int_t ngx_js_console_proto_id;
+#if (NJS_HAVE_QUICKJS)
+
+static const JSCFunctionListEntry ngx_qjs_ext_ngx[] = {
+ JS_CGETSET_DEF("build", ngx_qjs_ext_build, NULL),
+ JS_CGETSET_DEF("conf_prefix", ngx_qjs_ext_conf_prefix, NULL),
+ JS_CGETSET_DEF("conf_file_path", ngx_qjs_ext_conf_file_path, NULL),
+ JS_CGETSET_MAGIC_DEF("ERR", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_ERR),
+ JS_CGETSET_DEF("error_log_path", ngx_qjs_ext_error_log_path, NULL),
+ JS_CGETSET_MAGIC_DEF("INFO", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_INFO),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, 0),
+ JS_CGETSET_DEF("prefix", ngx_qjs_ext_prefix, NULL),
+ JS_PROP_STRING_DEF("version", NGINX_VERSION, JS_PROP_C_W_E),
+ JS_PROP_INT32_DEF("version_number", nginx_version, JS_PROP_C_W_E),
+ JS_CGETSET_MAGIC_DEF("WARN", ngx_qjs_ext_constant_integer, NULL,
+ NGX_LOG_WARN),
+ JS_CGETSET_DEF("worker_id", ngx_qjs_ext_worker_id, NULL),
+};
+
+
+static const JSCFunctionListEntry ngx_qjs_ext_console[] = {
+ JS_CFUNC_MAGIC_DEF("error", 1, ngx_qjs_ext_log, NGX_LOG_ERR),
+ JS_CFUNC_MAGIC_DEF("info", 1, ngx_qjs_ext_log, NGX_LOG_INFO),
+ JS_CFUNC_MAGIC_DEF("log", 1, ngx_qjs_ext_log, NGX_LOG_INFO),
+ JS_CFUNC_DEF("time", 1, ngx_qjs_ext_console_time),
+ JS_CFUNC_DEF("timeEnd", 1, ngx_qjs_ext_console_time_end),
+ JS_CFUNC_MAGIC_DEF("warn", 1, ngx_qjs_ext_log, NGX_LOG_WARN),
+};
+
+
+static const JSCFunctionListEntry ngx_qjs_ext_global[] = {
+ JS_CFUNC_MAGIC_DEF("setTimeout", 1, ngx_qjs_ext_set_timeout, 0),
+ JS_CFUNC_MAGIC_DEF("setImmediate", 1, ngx_qjs_ext_set_timeout, 1),
+ JS_CFUNC_DEF("clearTimeout", 1, ngx_qjs_ext_clear_timeout),
+};
+
+
+static JSClassDef ngx_qjs_console_class = {
+ "Console",
+ .finalizer = ngx_qjs_console_finalizer,
+};
+
+
+qjs_module_t ngx_qjs_ngx_module = {
+ .name = "ngx",
+ .init = ngx_qjs_core_init,
+};
+
+#endif
+
static ngx_engine_t *
ngx_create_engine(ngx_engine_opts_t *opts)
{
@@ -415,6 +514,25 @@ ngx_create_engine(ngx_engine_opts_t *opts)
: ngx_engine_njs_destroy;
break;
+#if (NJS_HAVE_QUICKJS)
+ case NGX_ENGINE_QJS:
+ rc = ngx_engine_qjs_init(engine, opts);
+ if (rc != NGX_OK) {
+ return NULL;
+ }
+
+ engine->name = "QuickJS";
+ engine->type = NGX_ENGINE_QJS;
+ engine->compile = ngx_engine_qjs_compile;
+ engine->call = ngx_engine_qjs_call;
+ engine->external = ngx_engine_qjs_external;
+ engine->pending = ngx_engine_qjs_pending;
+ engine->string = ngx_engine_qjs_string;
+ engine->destroy = opts->destroy ? opts->destroy
+ : ngx_engine_qjs_destroy;
+ break;
+#endif
+
default:
return NULL;
}
@@ -700,6 +818,1386 @@ ngx_engine_njs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
}
+#if (NJS_HAVE_QUICKJS)
+
+static ngx_int_t
+ngx_engine_qjs_init(ngx_engine_t *engine, ngx_engine_opts_t *opts)
+{
+ JSRuntime *rt;
+
+ rt = JS_NewRuntime();
+ if (rt == NULL) {
+ return NGX_ERROR;
+ }
+
+ engine->u.qjs.ctx = qjs_new_context(rt, opts->u.qjs.addons);
+ if (engine->u.qjs.ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ JS_SetRuntimeOpaque(rt, opts->u.qjs.metas);
+ JS_SetContextOpaque(engine->u.qjs.ctx, opts->u.qjs.addons);
+
+ JS_SetModuleLoaderFunc(rt, NULL, ngx_qjs_module_loader, opts->conf);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_compile(ngx_js_loc_conf_t *conf, ngx_log_t *log, u_char *start,
+ size_t size)
+{
+ JSValue code;
+ ngx_str_t text;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_js_code_entry_t *pc;
+
+ engine = conf->engine;
+ cx = engine->u.qjs.ctx;
+
+ code = JS_Eval(cx, (char *) start, size, "<main>",
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+
+ if (JS_IsException(code)) {
+ ngx_qjs_exception(engine, &text);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "js compile %V", &text);
+ return NGX_ERROR;
+ }
+
+ pc = njs_arr_add(engine->precompiled);
+ if (pc == NULL) {
+ JS_FreeValue(cx, code);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "njs_arr_add() failed");
+ return NGX_ERROR;
+ }
+
+ pc->code = JS_WriteObject(cx, &pc->code_size, code, JS_WRITE_OBJ_BYTECODE);
+ if (pc->code == NULL) {
+ JS_FreeValue(cx, code);
+ ngx_log_error(NGX_LOG_EMERG, log, 0, "JS_WriteObject() failed");
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, code);
+
+ return NGX_OK;
+}
+
+
+static JSValue
+js_std_await(JSContext *ctx, JSValue obj)
+{
+ int state, err;
+ JSValue ret;
+ JSContext *ctx1;
+
+ for (;;) {
+ state = JS_PromiseState(ctx, obj);
+ if (state == JS_PROMISE_FULFILLED) {
+ ret = JS_PromiseResult(ctx, obj);
+ JS_FreeValue(ctx, obj);
+ break;
+
+ } else if (state == JS_PROMISE_REJECTED) {
+ ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj));
+ JS_FreeValue(ctx, obj);
+ break;
+
+ } else if (state == JS_PROMISE_PENDING) {
+ err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
+ if (err < 0) {
+ /* js_std_dump_error(ctx1); */
+ }
+
+ } else {
+ /* not a promise */
+ ret = obj;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+
+ngx_engine_t *
+ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
+{
+ JSValue rv;
+ njs_mp_t *mp;
+ uint32_t i, length;
+ JSRuntime *rt;
+ ngx_str_t exception;
+ JSContext *cx;
+ ngx_engine_t *engine;
+ ngx_js_code_entry_t *pc;
+
+ mp = njs_mp_fast_create(2 * getpagesize(), 128, 512, 16);
+ if (mp == NULL) {
+ return NULL;
+ }
+
+ engine = njs_mp_alloc(mp, sizeof(ngx_engine_t));
+ if (engine == NULL) {
+ return NULL;
+ }
+
+ memcpy(engine, cf->engine, sizeof(ngx_engine_t));
+ engine->pool = mp;
+
+ if (cf->reuse_queue != NULL) {
+ engine->u.qjs.ctx = ngx_js_queue_pop(cf->reuse_queue);
+ if (engine->u.qjs.ctx != NULL) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+ "js reused context: %p", engine->u.qjs.ctx);
+ JS_SetContextOpaque(engine->u.qjs.ctx, external);
+ return engine;
+ }
+ }
+
+ rt = JS_NewRuntime();
+ if (rt == NULL) {
+ return NULL;
+ }
+
+ JS_SetRuntimeOpaque(rt, JS_GetRuntimeOpaque(
+ JS_GetRuntime(cf->engine->u.qjs.ctx)));
+
+ cx = qjs_new_context(rt, JS_GetContextOpaque(cf->engine->u.qjs.ctx));
+ if (cx == NULL) {
+ JS_FreeRuntime(rt);
+ return NULL;
+ }
+
+ engine->u.qjs.ctx = cx;
+ JS_SetContextOpaque(cx, external);
+
+ JS_SetHostPromiseRejectionTracker(rt, ngx_qjs_rejection_tracker, ctx);
+
+
+ /* TODO: bind objects from preload vm */
+
+ rv = JS_UNDEFINED;
+ pc = engine->precompiled->start;
+ length = engine->precompiled->items;
+
+ for (i = 0; i < length; i++) {
+ rv = JS_ReadObject(cx, pc[i].code, pc[i].code_size,
+ JS_READ_OBJ_BYTECODE);
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js load module exception: %V", &exception);
+ goto destroy;
+ }
+ }
+
+ if (JS_ResolveModule(cx, rv) < 0) {
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js resolve module failed");
+ goto destroy;
+ }
+
+ rv = JS_EvalFunction(cx, rv);
+
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V",
+ &exception);
+ goto destroy;
+ }
+
+ rv = js_std_await(cx, rv);
+ if (JS_IsException(rv)) {
+ ngx_qjs_exception(engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js eval exception: %V",
+ &exception);
+ goto destroy;
+ }
+
+ JS_FreeValue(cx, rv);
+
+ return engine;
+
+destroy:
+
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+ njs_mp_destroy(mp);
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_call(ngx_js_ctx_t *ctx, ngx_str_t *fname,
+ njs_opaque_value_t *args, njs_uint_t nargs)
+{
+ int rc;
+ JSValue fn, val;
+ ngx_str_t exception;
+ JSRuntime *rt;
+ JSContext *cx, *cx1;
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ fn = ngx_qjs_value(cx, fname);
+ if (!JS_IsFunction(cx, fn)) {
+ JS_FreeValue(cx, fn);
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js function \"%V\" not found",
+ fname);
+
+ return NGX_ERROR;
+ }
+
+ val = JS_Call(cx, fn, JS_UNDEFINED, nargs, &ngx_qjs_arg(args[0]));
+ JS_FreeValue(cx, fn);
+ if (JS_IsException(val)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js call exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+ ngx_qjs_arg(ctx->retval) = val;
+
+ rt = JS_GetRuntime(cx);
+
+ for ( ;; ) {
+ rc = JS_ExecutePendingJob(rt, &cx1);
+ if (rc <= 0) {
+ if (rc == -1) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js job exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ break;
+ }
+ }
+
+ if (ngx_qjs_unhandled_rejection(ctx)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "js exception: %V", &exception);
+ return NGX_ERROR;
+ }
+
+ return njs_rbtree_is_empty(&ctx->waiting_events) ? NGX_OK : NGX_AGAIN;
+}
+
+
+static void *
+ngx_engine_qjs_external(ngx_engine_t *e)
+{
+ return JS_GetContextOpaque(e->u.qjs.ctx);
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_pending(ngx_engine_t *e)
+{
+ return JS_IsJobPending(JS_GetRuntime(e->u.qjs.ctx));
+}
+
+
+static ngx_int_t
+ngx_engine_qjs_string(ngx_engine_t *e, njs_opaque_value_t *value,
+ ngx_str_t *str)
+{
+ return ngx_qjs_dump_obj(e, ngx_qjs_arg(*value), str);
+}
+
+
+static void
+ngx_js_cleanup_reuse_ctx(void *data)
+{
+ JSRuntime *rt;
+ JSContext *cx;
+
+ ngx_js_queue_t *reuse = data;
+
+ for ( ;; ) {
+ cx = ngx_js_queue_pop(reuse);
+ if (cx == NULL) {
+ break;
+ }
+
+ rt = JS_GetRuntime(cx);
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+ }
+}
+
+
+void
+ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx,
+ ngx_js_loc_conf_t *conf)
+{
+ uint32_t i, length;
+ JSRuntime *rt;
+ JSContext *cx;
+ JSClassID class_id;
+ ngx_qjs_event_t *event;
+ ngx_js_opaque_t *opaque;
+ njs_rbtree_node_t *node;
+ ngx_pool_cleanup_t *cln;
+ ngx_js_code_entry_t *pc;
+ ngx_js_rejected_promise_t *rejected_promise;
+
+ cx = e->u.qjs.ctx;
+
+ if (ctx != NULL) {
+ node = njs_rbtree_min(&ctx->waiting_events);
+
+ while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) {
+ event = (ngx_qjs_event_t *) ((u_char *) node
+ - offsetof(ngx_qjs_event_t, node));
+
+ if (event->destructor != NULL) {
+ event->destructor(event);
+ }
+
+ node = njs_rbtree_node_successor(&ctx->waiting_events, node);
+ }
+
+ if (ctx->rejected_promises != NULL) {
+ rejected_promise = ctx->rejected_promises->start;
+
+ for (i = 0; i < ctx->rejected_promises->items; i++) {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
+ }
+ }
+
+ class_id = JS_GetClassID(ngx_qjs_arg(ctx->args[0]));
+ opaque = JS_GetOpaque(ngx_qjs_arg(ctx->args[0]), class_id);
+ opaque->external = NULL;
+
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->args[0]));
+ JS_FreeValue(cx, ngx_qjs_arg(ctx->retval));
+
+ } else if (e->precompiled != NULL) {
+ pc = e->precompiled->start;
+ length = e->precompiled->items;
+
+ for (i = 0; i < length; i++) {
+ js_free(cx, pc[i].code);
+ }
+ }
+
+ njs_mp_destroy(e->pool);
+
+ if (conf != NULL && conf->reuse != 0) {
+ if (conf->reuse_queue == NULL) {
+ conf->reuse_queue = ngx_js_queue_create(ngx_cycle->pool,
+ conf->reuse);
+ if (conf->reuse_queue == NULL) {
+ goto free_ctx;
+ }
+
+ cln = ngx_pool_cleanup_add(ngx_cycle->pool, 0);
+ if (cln == NULL) {
+ goto free_ctx;
+ }
+
+ cln->handler = ngx_js_cleanup_reuse_ctx;
+ cln->data = conf->reuse_queue;
+ }
+
+ if (ngx_js_queue_push(conf->reuse_queue, cx) != NGX_OK) {
+ goto free_ctx;
+ }
+
+ return;
+ }
+
+free_ctx:
+
+ rt = JS_GetRuntime(cx);
+ JS_FreeContext(cx);
+ JS_FreeRuntime(rt);
+}
+
+
+static JSValue
+ngx_qjs_value(JSContext *cx, const ngx_str_t *path)
+{
+ u_char *start, *p, *end;
+ JSAtom key;
+ size_t size;
+ JSValue value, rv;
+
+ start = path->data;
+ end = start + path->len;
+
+ value = JS_GetGlobalObject(cx);
+
+ for ( ;; ) {
+ p = njs_strlchr(start, end, '.');
+
+ size = ((p != NULL) ? p : end) - start;
+ if (size == 0) {
+ JS_FreeValue(cx, value);
+ return JS_ThrowTypeError(cx, "empty path element");
+ }
+
+ key = JS_NewAtomLen(cx, (char *) start, size);
+ if (key == JS_ATOM_NULL) {
+ JS_FreeValue(cx, value);
+ return JS_ThrowInternalError(cx, "could not create atom");
+ }
+
+ rv = JS_GetProperty(cx, value, key);
+ JS_FreeAtom(cx, key);
+ if (JS_IsException(rv)) {
+ JS_FreeValue(cx, value);
+ return JS_EXCEPTION;
+ }
+
+ JS_FreeValue(cx, value);
+
+ if (p == NULL) {
+ break;
+ }
+
+ start = p + 1;
+ value = rv;
+ }
+
+ return rv;
+}
+
+
+static ngx_int_t
+ngx_qjs_dump_obj(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst)
+{
+ size_t len, byte_offset, byte_length;
+ u_char *start, *p;
+ JSValue buffer, stack;
+ ngx_str_t str, stack_str;
+ JSContext *cx;
+
+ if (JS_IsNullOrUndefined(val)) {
+ dst->data = NULL;
+ dst->len = 0;
+ return NGX_OK;
+ }
+
+ cx = e->u.qjs.ctx;
+
+ buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buffer)) {
+ start = JS_GetArrayBuffer(cx, &dst->len, buffer);
+
+ JS_FreeValue(cx, buffer);
+
+ if (start != NULL) {
+ start += byte_offset;
+ dst->len = byte_length;
+
+ dst->data = njs_mp_alloc(e->pool, dst->len);
+ if (dst->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(dst->data, start, dst->len);
+ return NGX_OK;
+ }
+ }
+
+ str.data = (u_char *) JS_ToCString(cx, val);
+ if (str.data != NULL) {
+ str.len = ngx_strlen(str.data);
+
+ stack = JS_GetPropertyStr(cx, val, "stack");
+
+ stack_str.len = 0;
+ stack_str.data = NULL;
+
+ if (!JS_IsException(stack) && !JS_IsUndefined(stack)) {
+ stack_str.data = (u_char *) JS_ToCString(cx, stack);
+ if (stack_str.data != NULL) {
+ stack_str.len = ngx_strlen(stack_str.data);
+ }
+ }
+
+ len = str.len;
+
+ if (stack_str.len != 0) {
+ len += stack_str.len + njs_length("\n");
+ }
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ JS_FreeCString(cx, (char *) str.data);
+ JS_FreeValue(cx, stack);
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(start, str.data, str.len);
+
+ if (stack_str.len != 0) {
+ *p++ = '\n';
+ (void) ngx_cpymem(p, stack_str.data, stack_str.len);
+ JS_FreeCString(cx, (char *) stack_str.data);
+ }
+
+ JS_FreeCString(cx, (char *) str.data);
+ JS_FreeValue(cx, stack);
+
+ } else {
+ len = njs_length("[exception]");
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(start, "[exception]", len);
+ }
+
+ dst->data = start;
+ dst->len = len;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_call(ngx_js_ctx_t *ctx, JSValue fn, JSValue *argv, int argc)
+{
+ int rc;
+ JSValue ret;
+ ngx_str_t exception;
+ JSRuntime *rt;
+ JSContext *cx, *cx1;
+
+ cx = ctx->engine->u.qjs.ctx;
+
+ ret = JS_Call(cx, fn, JS_UNDEFINED, argc, argv);
+ if (JS_IsException(ret)) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js call exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(cx, ret);
+
+ rt = JS_GetRuntime(cx);
+
+ for ( ;; ) {
+ rc = JS_ExecutePendingJob(rt, &cx1);
+ if (rc <= 0) {
+ if (rc == -1) {
+ ngx_qjs_exception(ctx->engine, &exception);
+
+ ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+ "js job exception: %V", &exception);
+
+ return NGX_ERROR;
+ }
+
+ break;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s)
+{
+ JSValue exception;
+
+ exception = JS_GetException(e->u.qjs.ctx);
+ if (ngx_qjs_dump_obj(e, exception, s) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ JS_FreeValue(e->u.qjs.ctx, exception);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n)
+{
+ double num;
+
+ if (JS_ToFloat64(cx, &num, val)) {
+ return NGX_ERROR;
+ }
+
+ if (isinf(num) || isnan(num)) {
+ (void) JS_ThrowTypeError(cx, "invalid number");
+ return NGX_ERROR;
+ }
+
+ *n = num;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_qjs_string(ngx_engine_t *e, JSValueConst val, ngx_str_t *dst)
+{
+ size_t len, byte_offset, byte_length;
+ u_char *start;
+ JSValue buffer;
+ JSContext *cx;
+ const char *str;
+
+ if (JS_IsNullOrUndefined(val)) {
+ dst->data = NULL;
+ dst->len = 0;
+ return NGX_OK;
+ }
+
+ cx = e->u.qjs.ctx;
+
+ buffer = JS_GetTypedArrayBuffer(cx, val, &byte_offset, &byte_length, NULL);
+ if (!JS_IsException(buffer)) {
+ start = JS_GetArrayBuffer(cx, &dst->len, buffer);
+
+ JS_FreeValue(cx, buffer);
+
+ if (start != NULL) {
+ start += byte_offset;
+ dst->len = byte_length;
+
+ dst->data = njs_mp_alloc(e->pool, dst->len);
+ if (dst->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ memcpy(dst->data, start, dst->len);
+ return NGX_OK;
+ }
+ }
+
+ str = JS_ToCString(cx, val);
+ if (str == NULL) {
+ return NGX_ERROR;
+ }
+
+ len = strlen(str);
+
+ start = njs_mp_alloc(e->pool, len);
+ if (start == NULL) {
+ JS_FreeCString(cx, str);
+ return NGX_ERROR;
+ }
+
+ memcpy(start, str, len);
+
+ JS_FreeCString(cx, str);
+
+ dst->data = start;
+ dst->len = len;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_qjs_timer_handler(ngx_event_t *ev)
+{
+ void *external;
+ JSContext *cx;
+ ngx_int_t rc;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t *event;
+
+ event = (ngx_qjs_event_t *) ((u_char *) ev - offsetof(ngx_qjs_event_t, ev));
+
+ cx = event->ctx;
+ external = JS_GetContextOpaque(cx);
+ ctx = ngx_qjs_external_ctx(cx, external);
+
+ rc = ngx_qjs_call((ngx_js_ctx_t *) ctx, event->function, event->args,
+ event->nargs);
+
+ ngx_js_del_event(ctx, event);
+
+ ngx_qjs_external_event_finalize(cx)(external, rc);
+}
+
+
+static void
+ngx_qjs_clear_timer(ngx_qjs_event_t *event)
+{
+ int i;
+ JSContext *cx;
+
+ cx = event->ctx;
+
+ if (event->ev.timer_set) {
+ ngx_del_timer(&event->ev);
+ }
+
+ JS_FreeValue(cx, event->function);
+
+ for (i = 0; i < (int) event->nargs; i++) {
+ JS_FreeValue(cx, event->args[i]);
+ }
+}
+
+
+static JSValue
+ngx_qjs_ext_set_timeout(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int immediate)
+{
+ int i, n;
+ void *external;
+ uint32_t delay;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t *event;
+ ngx_connection_t *c;
+
+ if (!JS_IsFunction(cx, argv[0])) {
+ return JS_ThrowTypeError(cx, "first arg must be a function");
+ }
+
+ delay = 0;
+
+ if (!immediate && argc >= 2) {
+ if (JS_ToUint32(cx, &delay, argv[1]) < 0) {
+ return JS_EXCEPTION;
+ }
+ }
+
+ n = immediate ? 1 : 2;
+ argc = (argc >= n) ? argc - n : 0;
+ external = JS_GetContextOpaque(cx);
+ ctx = ngx_qjs_external_ctx(cx, external);
+
+ event = ngx_pcalloc(ngx_qjs_external_pool(cx, external),
+ sizeof(ngx_qjs_event_t) + sizeof(JSValue) * argc);
+ if (event == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ event->ctx = cx;
+ event->function = JS_DupValue(cx, argv[0]);
+ event->nargs = argc;
+ event->args = (JSValue *) &event[1];
+ event->destructor = ngx_qjs_clear_timer;
+ event->fd = ctx->event_id++;
+
+ c = ngx_qjs_external_connection(cx, external);
+
+ event->ev.log = c->log;
+ event->ev.data = event;
+ event->ev.handler = ngx_qjs_timer_handler;
+
+ if (event->nargs != 0) {
+ for (i = 0; i < argc; i++) {
+ event->args[i] = JS_DupValue(cx, argv[n + i]);
+ }
+ }
+
+ ngx_js_add_event(ctx, event);
+
+ ngx_add_timer(&event->ev, delay);
+
+ return JS_NewInt32(cx, event->fd);
+}
+
+
+static JSValue
+ngx_qjs_ext_clear_timeout(JSContext *cx, JSValueConst this_val,
+ int argc, JSValueConst *argv)
+{
+ uint32_t id;
+ ngx_js_ctx_t *ctx;
+ ngx_qjs_event_t event_lookup, *event;
+ njs_rbtree_node_t *rb;
+
+ if (JS_ToUint32(cx, &id, argv[0]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ ctx = ngx_qjs_external_ctx(cx, JS_GetContextOpaque(cx));
+ event_lookup.fd = id;
+
+ rb = njs_rbtree_find(&ctx->waiting_events, &event_lookup.node);
+ if (rb == NULL) {
+ return JS_ThrowReferenceError(cx, "failed to find timer");
+ }
+
+ event = (ngx_qjs_event_t *) ((u_char *) rb
+ - offsetof(ngx_qjs_event_t, node));
+
+ ngx_js_del_event(ctx, event);
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_build(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx,
+#ifdef NGX_BUILD
+ (char *) NGX_BUILD,
+ njs_strlen(NGX_BUILD)
+#else
+ (char *) "",
+ 0
+#endif
+ );
+}
+
+
+static JSValue
+ngx_qjs_ext_conf_prefix(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data,
+ ngx_cycle->prefix.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_conf_file_path(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->conf_file.data,
+ ngx_cycle->conf_file.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_constant_integer(JSContext *cx, JSValueConst this_val, int magic)
+{
+ return JS_NewInt32(cx, magic);
+}
+
+
+static JSValue
+ngx_qjs_ext_error_log_path(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->error_log.data,
+ ngx_cycle->error_log.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_prefix(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewStringLen(cx, (char *) ngx_cycle->prefix.data,
+ ngx_cycle->prefix.len);
+}
+
+
+static JSValue
+ngx_qjs_ext_worker_id(JSContext *cx, JSValueConst this_val)
+{
+ return JS_NewInt32(cx, ngx_worker);
+}
+
+
+static void
+ngx_qjs_console_finalizer(JSRuntime *rt, JSValue val)
+{
+ ngx_queue_t *labels, *q, *next;
+ ngx_js_console_t *console;
+ ngx_js_timelabel_t *label;
+
+ console = JS_GetOpaque(val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == (void *) 1) {
+ return;
+ }
+
+ labels = &console->labels;
+ q = ngx_queue_head(labels);
+
+ for ( ;; ) {
+ if (q == ngx_queue_sentinel(labels)) {
+ break;
+ }
+
+ next = ngx_queue_next(q);
+
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+ ngx_queue_remove(&label->queue);
+ js_free_rt(rt, label);
+
+ q = next;
+ }
+
+ js_free_rt(rt, console);
+}
+
+
+static JSValue
+ngx_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv, int magic)
+{
+ char *p;
+ uint32_t level;
+ ngx_str_t msg;
+ ngx_js_ctx_t *ctx;
+ ngx_connection_t *c;
+
+ p = JS_GetContextOpaque(cx);
+ if (p == NULL) {
+ return JS_ThrowInternalError(cx, "external is not set");
+ }
+
+ level = magic & NGX_JS_LOG_MASK;
+
+ if (level == 0) {
+ if (JS_ToUint32(cx, &level, argv[0]) < 0) {
+ return JS_EXCEPTION;
+ }
+
+ argc--;
+ argv++;
+ }
+
+ ctx = ngx_qjs_external_ctx(cx, p);
+ c = ngx_qjs_external_connection(cx, p);
+
+ for ( ; argc > 0; argc--, argv++) {
+ if (ngx_qjs_dump_obj(ctx->engine, argv[0], &msg) != NGX_OK) {
+ return JS_EXCEPTION;
+ }
+
+ ngx_js_logger(c, level, (u_char *) msg.data, msg.len);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_console_time(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ ngx_str_t name;
+ ngx_queue_t *labels, *q;
+ ngx_js_console_t *console;
+ ngx_connection_t *c;
+ ngx_js_timelabel_t *label;
+
+ static const ngx_str_t default_label = ngx_string("default");
+
+ console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == NULL) {
+ return JS_ThrowInternalError(cx, "this is not a console object");
+ }
+
+ if (console == (void *) 1) {
+ console = js_malloc(cx, sizeof(ngx_js_console_t));
+ if (console == NULL) {
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ ngx_queue_init(&console->labels);
+
+ JS_SetOpaque(this_val, console);
+ }
+
+ if (!JS_IsUndefined(argv[0])) {
+ name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]);
+ if (name.data == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ } else {
+ name = default_label;
+ }
+
+ labels = &console->labels;
+
+ for (q = ngx_queue_head(labels);
+ q != ngx_queue_sentinel(labels);
+ q = ngx_queue_next(q))
+ {
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+ if (name.len == label->name.length
+ && ngx_strncmp(name.data, label->name.start, name.len) == 0)
+ {
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" already"
+ " exists", &name);
+
+ goto done;
+ }
+ }
+
+ label = js_malloc(cx, sizeof(ngx_js_timelabel_t) + name.len);
+ if (label == NULL) {
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_ThrowOutOfMemory(cx);
+ }
+
+ label->name.length = name.len;
+ label->name.start = (u_char *) label + sizeof(ngx_js_timelabel_t);
+ memcpy(label->name.start, name.data, name.len);
+
+ label->time = ngx_js_monotonic_time();
+
+ ngx_queue_insert_tail(&console->labels, &label->queue);
+
+done:
+
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSValue
+ngx_qjs_ext_console_time_end(JSContext *cx, JSValueConst this_val, int argc,
+ JSValueConst *argv)
+{
+ uint64_t ns, ms;
+ ngx_str_t name;
+ ngx_queue_t *labels, *q;
+ ngx_js_console_t *console;
+ ngx_connection_t *c;
+ ngx_js_timelabel_t *label;
+
+ static const ngx_str_t default_label = ngx_string("default");
+
+ ns = ngx_js_monotonic_time();
+
+ console = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_CONSOLE);
+ if (console == NULL) {
+ return JS_ThrowInternalError(cx, "this is not a console object");
+ }
+
+ if (!JS_IsUndefined(argv[0])) {
+ name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]);
+ if (name.data == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ } else {
+ name = default_label;
+ }
+
+ if (console == (void *) 1) {
+ goto not_found;
+ }
+
+ labels = &console->labels;
+ q = ngx_queue_head(labels);
+
+ for ( ;; ) {
+ if (q == ngx_queue_sentinel(labels)) {
+ goto not_found;
+ }
+
+ label = ngx_queue_data(q, ngx_js_timelabel_t, queue);
+
+ if (name.len == label->name.length
+ && ngx_strncmp(name.data, label->name.start, name.len) == 0)
+ {
+ ngx_queue_remove(&label->queue);
+ break;
+ }
+
+ q = ngx_queue_next(q);
+ }
+
+ ns = ns - label->time;
+
+ js_free(cx, label);
+
+ ms = ns / 1000000;
+ ns = ns % 1000000;
+
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: %V: %uL.%06uLms",
+ &name, ms, ns);
+
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_UNDEFINED;
+
+not_found:
+
+ c = ngx_qjs_external_connection(cx, JS_GetContextOpaque(cx));
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "js: Timer \"%V\" doesn't exist",
+ &name);
+
+ if (name.data != default_label.data) {
+ JS_FreeCString(cx, (char *) name.data);
+ }
+
+ return JS_UNDEFINED;
+}
+
+
+static JSModuleDef *
+ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque)
+{
+ JSValue func_val;
+ njs_int_t ret;
+ njs_str_t text;
+ JSModuleDef *m;
+ njs_module_info_t info;
+ ngx_js_loc_conf_t *conf;
+ ngx_js_code_entry_t *pc;
+
+ conf = opaque;
+
+ njs_memzero(&info, sizeof(njs_module_info_t));
+
+ info.name.start = (u_char *) module_name;
+ info.name.length = njs_strlen(module_name);
+
+ ret = ngx_js_module_lookup(conf, &info);
+ if (ret != NJS_OK) {
+ JS_ThrowReferenceError(cx, "could not load module filename '%s'",
+ module_name);
+ return NULL;
+ }
+
+ ret = ngx_js_module_read(conf->engine->pool, info.fd, &text);
+
+ (void) close(info.fd);
+
+ if (ret != NJS_OK) {
+ JS_ThrowInternalError(cx, "while reading \"%*s\" module",
+ (int) info.file.length, info.file.start);
+ return NULL;
+ }
+
+ func_val = JS_Eval(cx, (char *) text.start, text.length, module_name,
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+
+ njs_mp_free(conf->engine->pool, text.start);
+
+ if (JS_IsException(func_val)) {
+ return NULL;
+ }
+
+ if (conf->engine->precompiled == NULL) {
+ conf->engine->precompiled = njs_arr_create(conf->engine->pool, 4,
+ sizeof(ngx_js_code_entry_t));
+ if (conf->engine->precompiled == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+ }
+
+ pc = njs_arr_add(conf->engine->precompiled);
+ if (pc == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowOutOfMemory(cx);
+ return NULL;
+ }
+
+ pc->code = JS_WriteObject(cx, &pc->code_size, func_val,
+ JS_WRITE_OBJ_BYTECODE);
+ if (pc->code == NULL) {
+ JS_FreeValue(cx, func_val);
+ JS_ThrowInternalError(cx, "could not write module bytecode");
+ return NULL;
+ }
+
+ m = JS_VALUE_GET_PTR(func_val);
+ JS_FreeValue(cx, func_val);
+
+ return m;
+}
+
+
+static int
+ngx_qjs_unhandled_rejection(ngx_js_ctx_t *ctx)
+{
+ size_t len;
+ uint32_t i;
+ JSContext *cx;
+ const char *str;
+ ngx_js_rejected_promise_t *rejected_promise;
+
+ if (ctx->rejected_promises == NULL
+ || ctx->rejected_promises->items == 0)
+ {
+ return 0;
+ }
+
+ cx = ctx->engine->u.qjs.ctx;
+ rejected_promise = ctx->rejected_promises->start;
+
+ str = JS_ToCStringLen(cx, &len, ngx_qjs_arg(rejected_promise->message));
+ if (njs_slow_path(str == NULL)) {
+ return -1;
+ }
+
+ JS_ThrowTypeError(cx, "unhandled promise rejection: %*s", (int) len, str);
+ JS_FreeCString(cx, str);
+
+ for (i = 0; i < ctx->rejected_promises->items; i++) {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
+ }
+
+ njs_arr_destroy(ctx->rejected_promises);
+ ctx->rejected_promises = NULL;
+
+ return 1;
+}
+
+
+static void
+ngx_qjs_rejection_tracker(JSContext *cx, JSValueConst promise,
+ JSValueConst reason, JS_BOOL is_handled, void *opaque)
+{
+ void *promise_obj;
+ uint32_t i, length;
+ ngx_js_ctx_t *ctx;
+ ngx_js_rejected_promise_t *rejected_promise;
+
+ ctx = opaque;
+
+ if (is_handled && ctx->rejected_promises != NULL) {
+ rejected_promise = ctx->rejected_promises->start;
+ length = ctx->rejected_promises->items;
+
+ promise_obj = JS_VALUE_GET_PTR(promise);
+
+ for (i = 0; i < length; i++) {
+ if (JS_VALUE_GET_PTR(ngx_qjs_arg(rejected_promise[i].promise))
+ == promise_obj)
+ {
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].promise));
+ JS_FreeValue(cx, ngx_qjs_arg(rejected_promise[i].message));
+ njs_arr_remove(ctx->rejected_promises, &rejected_promise[i]);
+
+ break;
+ }
+ }
+
+ return;
+ }
+
+ if (ctx->rejected_promises == NULL) {
+ if (ctx->engine == NULL) {
+ /* Do not track rejections during eval stage. The exception
+ * is lifted by the ngx_qjs_clone() function manually. */
+ return;
+ }
+
+ ctx->rejected_promises = njs_arr_create(ctx->engine->pool, 4,
+ sizeof(ngx_js_rejected_promise_t));
+ if (ctx->rejected_promises == NULL) {
+ return;
+ }
+ }
+
+ rejected_promise = njs_arr_add(ctx->rejected_promises);
+ if (rejected_promise == NULL) {
+ return;
+ }
+
+ ngx_qjs_arg(rejected_promise->promise) = JS_DupValue(cx, promise);
+ ngx_qjs_arg(rejected_promise->message) = JS_DupValue(cx, reason);
+}
+
+
+static JSModuleDef *
+ngx_qjs_core_init(JSContext *cx, const char *name)
+{
+ int ret;
+ JSValue global_obj, proto, obj;
+ JSModuleDef *m;
+
+ if (!JS_IsRegisteredClass(JS_GetRuntime(cx),
+ NGX_QJS_CLASS_ID_CONSOLE))
+ {
+ if (JS_NewClass(JS_GetRuntime(cx), NGX_QJS_CLASS_ID_CONSOLE,
+ &ngx_qjs_console_class) < 0)
+ {
+ return NULL;
+ }
+
+ proto = JS_NewObject(cx);
+ if (JS_IsException(proto)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, proto, ngx_qjs_ext_console,
+ njs_nitems(ngx_qjs_ext_console));
+
+ JS_SetClassProto(cx, NGX_QJS_CLASS_ID_CONSOLE, proto);
+ }
+
+ obj = JS_NewObject(cx);
+ if (JS_IsException(obj)) {
+ return NULL;
+ }
+
+ JS_SetPropertyFunctionList(cx, obj, ngx_qjs_ext_ngx,
+ njs_nitems(ngx_qjs_ext_ngx));
+
+ global_obj = JS_GetGlobalObject(cx);
+
+ JS_SetPropertyFunctionList(cx, global_obj, ngx_qjs_ext_global,
+ njs_nitems(ngx_qjs_ext_global));
+
+ ret = JS_SetPropertyStr(cx, global_obj, "ngx", obj);
+ if (ret < 0) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
+ }
+
+ obj = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_CONSOLE);
+ if (JS_IsException(obj)) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
+ }
+
+ JS_SetOpaque(obj, (void *) 1);
+
+ ret = JS_SetPropertyStr(cx, global_obj, "console", obj);
+ if (ret < 0) {
+ JS_FreeValue(cx, global_obj);
+ return NULL;
+ }
+
+ JS_FreeValue(cx, global_obj);
+
+ m = JS_NewCModule(cx, name, NULL);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ return m;
+}
+
+#endif
+
+
ngx_int_t
ngx_js_call(njs_vm_t *vm, njs_function_t *func, njs_opaque_value_t *args,
njs_uint_t nargs)
@@ -2439,11 +3937,18 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size)
return NULL;
}
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->reuse_queue = NULL;
+ */
+
conf->paths = NGX_CONF_UNSET_PTR;
conf->type = NGX_CONF_UNSET_UINT;
conf->imports = NGX_CONF_UNSET_PTR;
conf->preload_objects = NGX_CONF_UNSET_PTR;
+ conf->reuse = NGX_CONF_UNSET_SIZE;
conf->buffer_size = NGX_CONF_UNSET_SIZE;
conf->max_response_body_size = NGX_CONF_UNSET_SIZE;
conf->timeout = NGX_CONF_UNSET_MSEC;
@@ -2507,6 +4012,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS);
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+ ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128);
ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384);
ngx_conf_merge_size_value(conf->max_response_body_size,
prev->max_response_body_size, 1048576);
@@ -2562,3 +4068,59 @@ ngx_js_monotonic_time(void)
return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
#endif
}
+
+
+ngx_js_queue_t *
+ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity)
+{
+ ngx_js_queue_t *queue;
+
+ queue = ngx_pcalloc(pool, sizeof(ngx_js_queue_t));
+ if (queue == NULL) {
+ return NULL;
+ }
+
+ queue->data = ngx_pcalloc(pool, sizeof(void *) * capacity);
+ if (queue->data == NULL) {
+ return NULL;
+ }
+
+ queue->head = 0;
+ queue->tail = 0;
+ queue->size = 0;
+ queue->capacity = capacity;
+
+ return queue;
+}
+
+
+ngx_int_t
+ngx_js_queue_push(ngx_js_queue_t *queue, void *item)
+{
+ if (queue->size >= queue->capacity) {
+ return NGX_ERROR;
+ }
+
+ queue->data[queue->tail] = item;
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ queue->size++;
+
+ return NGX_OK;
+}
+
+
+void *
+ngx_js_queue_pop(ngx_js_queue_t *queue)
+{
+ void *item;
+
+ if (queue->size == 0) {
+ return NULL;
+ }
+
+ item = queue->data[queue->head];
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->size--;
+
+ return item;
+}