From: Dmitry Volyntsev Date: Wed, 21 Mar 2018 14:33:12 +0000 (+0300) Subject: setTimeout() and clearTimeout() methods. X-Git-Tag: 0.2.0~20 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=31c7dae5a940d93cf98ba73bb032a6d59083d579;p=njs.git setTimeout() and clearTimeout() methods. Public methods are introduced to create and post async events for a VM instance. njs_vm_add_event() creates an async event for the VM to wait for. njs_vm_post_event() notifies the VM that the event occurred. If async events were added njs_vm_run() returns NJS_AGAIN until there are no remaining pending events. --- diff --git a/Makefile b/Makefile index 21270546..23fbc335 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,9 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_date.o \ $(NXT_BUILDDIR)/njs_error.o \ $(NXT_BUILDDIR)/njs_math.o \ + $(NXT_BUILDDIR)/njs_time.o \ $(NXT_BUILDDIR)/njs_module.o \ + $(NXT_BUILDDIR)/njs_event.o \ $(NXT_BUILDDIR)/njs_fs.o \ $(NXT_BUILDDIR)/njs_extern.o \ $(NXT_BUILDDIR)/njs_variable.o \ @@ -56,7 +58,9 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_date.o \ $(NXT_BUILDDIR)/njs_error.o \ $(NXT_BUILDDIR)/njs_math.o \ + $(NXT_BUILDDIR)/njs_time.o \ $(NXT_BUILDDIR)/njs_module.o \ + $(NXT_BUILDDIR)/njs_event.o \ $(NXT_BUILDDIR)/njs_fs.o \ $(NXT_BUILDDIR)/njs_extern.o \ $(NXT_BUILDDIR)/njs_variable.o \ @@ -301,6 +305,18 @@ $(NXT_BUILDDIR)/njs_math.o: \ -I$(NXT_LIB) -Injs \ njs/njs_math.c +$(NXT_BUILDDIR)/njs_time.o: \ + $(NXT_BUILDDIR)/libnxt.a \ + njs/njscript.h \ + njs/njs_vm.h \ + njs/njs_object.h \ + njs/njs_time.h \ + njs/njs_time.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_time.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs_time.c + $(NXT_BUILDDIR)/njs_module.o: \ $(NXT_BUILDDIR)/libnxt.a \ njs/njscript.h \ @@ -312,6 +328,17 @@ $(NXT_BUILDDIR)/njs_module.o: \ -I$(NXT_LIB) -Injs \ njs/njs_module.c +$(NXT_BUILDDIR)/njs_event.o: \ + $(NXT_BUILDDIR)/libnxt.a \ + njs/njscript.h \ + njs/njs_vm.h \ + njs/njs_event.h \ + njs/njs_event.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_event.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs_event.c + $(NXT_BUILDDIR)/njs_fs.o: \ $(NXT_BUILDDIR)/libnxt.a \ njs/njscript.h \ diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 2a71c51b..0e707682 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -29,6 +29,7 @@ typedef struct { typedef struct { njs_vm_t *vm; + ngx_log_t *log; njs_opaque_value_t args[2]; } ngx_http_js_ctx_t; @@ -39,10 +40,21 @@ typedef struct { } ngx_http_js_table_entry_t; -static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r); +typedef struct { + ngx_http_request_t *request; + njs_vm_event_t vm_event; + void *unused; + ngx_int_t ident; +} ngx_http_js_event_t; + + +static ngx_int_t ngx_http_js_content_handler(ngx_http_request_t *r); +static void ngx_http_js_content_event_handler(ngx_http_request_t *r); +static void ngx_http_js_content_write_event_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r); +static void ngx_http_js_cleanup_ctx(void *data); static void ngx_http_js_cleanup_vm(void *data); static njs_ret_t ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value, @@ -94,6 +106,14 @@ static njs_ret_t ngx_http_js_ext_next_arg(njs_vm_t *vm, njs_value_t *value, static njs_ret_t ngx_http_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj, uintptr_t data); +static njs_host_event_t ngx_http_js_set_timer(njs_external_ptr_t external, + uint64_t delay, njs_vm_event_t vm_event); +static void ngx_http_js_clear_timer(njs_external_ptr_t external, + njs_host_event_t event); +static void ngx_http_js_timer_handler(ngx_event_t *ev); +static void ngx_http_js_handle_event(ngx_http_request_t *r, + njs_vm_event_t vm_event); + static char *ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -378,8 +398,33 @@ static njs_external_t ngx_http_js_externals[] = { }; +static njs_vm_ops_t ngx_http_js_ops = { + ngx_http_js_set_timer, + ngx_http_js_clear_timer +}; + + static ngx_int_t -ngx_http_js_handler(ngx_http_request_t *r) +ngx_http_js_content_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js content handler"); + + rc = ngx_http_read_client_request_body(r, + ngx_http_js_content_event_handler); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static void +ngx_http_js_content_event_handler(ngx_http_request_t *r) { ngx_int_t rc; nxt_str_t name, exception; @@ -387,10 +432,14 @@ ngx_http_js_handler(ngx_http_request_t *r) ngx_http_js_ctx_t *ctx; ngx_http_js_loc_conf_t *jlcf; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js content event handler"); + rc = ngx_http_js_init_vm(r); if (rc == NGX_ERROR || rc == NGX_DECLINED) { - return rc; + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; } jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); @@ -407,7 +456,8 @@ ngx_http_js_handler(ngx_http_request_t *r) if (func == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "js function \"%V\" not found", &jlcf->content); - return NGX_DECLINED; + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; } if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) { @@ -416,10 +466,66 @@ ngx_http_js_handler(ngx_http_request_t *r) ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "js exception: %*s", exception.length, exception.start); - return NGX_ERROR; + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; } - return NGX_OK; + if (njs_vm_pending(ctx->vm)) { + r->write_event_handler = ngx_http_js_content_write_event_handler; + return; + } + + ngx_http_finalize_request(r, NGX_OK); +} + + +static void +ngx_http_js_content_write_event_handler(ngx_http_request_t *r) +{ + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_js_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http js content write event handler"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + if (!njs_vm_pending(ctx->vm)) { + ngx_http_finalize_request(r, NGX_OK); + return; + } + + c = r->connection; + wev = c->write; + + if (wev->timedout) { + ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + if (ngx_http_output_filter(r, NULL) == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + + if (!wev->delayed) { + if (wev->active && !wev->ready) { + ngx_add_timer(wev, clcf->send_timeout); + + } else if (wev->timer_set) { + ngx_del_timer(wev); + } + } } @@ -430,6 +536,7 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, ngx_str_t *fname = (ngx_str_t *) data; ngx_int_t rc; + nxt_int_t pending; nxt_str_t name, value, exception; njs_function_t *func; ngx_http_js_ctx_t *ctx; @@ -461,6 +568,8 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, return NGX_OK; } + pending = njs_vm_pending(ctx->vm); + if (njs_vm_call(ctx->vm, func, ctx->args, 2) != NJS_OK) { njs_vm_retval_to_ext_string(ctx->vm, &exception); @@ -475,6 +584,12 @@ ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, return NGX_ERROR; } + if (!pending && njs_vm_pending(ctx->vm)) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "async operation inside \"%V\" variable handler", fname); + return NGX_ERROR; + } + v->len = value.length; v->valid = 1; v->no_cacheable = 0; @@ -489,6 +604,7 @@ static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r) { nxt_int_t rc; + nxt_str_t exception; ngx_http_js_ctx_t *ctx; ngx_pool_cleanup_t *cln; ngx_http_js_loc_conf_t *jlcf; @@ -523,10 +639,17 @@ ngx_http_js_init_vm(ngx_http_request_t *r) return NGX_ERROR; } - cln->handler = ngx_http_js_cleanup_vm; - cln->data = ctx->vm; + ctx->log = r->connection->log; + + cln->handler = ngx_http_js_cleanup_ctx; + cln->data = ctx; + + if (njs_vm_run(ctx->vm) == NJS_ERROR) { + njs_vm_retval_to_ext_string(ctx->vm, &exception); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "js exception: %*s", exception.length, exception.start); - if (njs_vm_run(ctx->vm) != NJS_OK) { return NGX_ERROR; } @@ -544,6 +667,19 @@ ngx_http_js_init_vm(ngx_http_request_t *r) } +static void +ngx_http_js_cleanup_ctx(void *data) +{ + ngx_http_js_ctx_t *ctx = data; + + if (njs_vm_pending(ctx->vm)) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); + } + + njs_vm_destroy(ctx->vm); +} + + static void ngx_http_js_cleanup_vm(void *data) { @@ -1157,6 +1293,98 @@ ngx_http_js_ext_get_variable(njs_vm_t *vm, njs_value_t *value, void *obj, } +static njs_host_event_t +ngx_http_js_set_timer(njs_external_ptr_t external, uint64_t delay, + njs_vm_event_t vm_event) +{ + ngx_event_t *ev; + ngx_http_request_t *r; + ngx_http_js_event_t *js_event; + + r = (ngx_http_request_t *) external; + + ev = ngx_pcalloc(r->pool, sizeof(ngx_event_t)); + if (ev == NULL) { + return NULL; + } + + js_event = ngx_palloc(r->pool, sizeof(ngx_http_js_event_t)); + if (js_event == NULL) { + return NULL; + } + + js_event->request = r; + js_event->vm_event = vm_event; + js_event->ident = r->connection->fd; + + ev->data = js_event; + ev->log = r->connection->log; + ev->handler = ngx_http_js_timer_handler; + + ngx_add_timer(ev, delay); + + return ev; +} + + +static void +ngx_http_js_clear_timer(njs_external_ptr_t external, njs_host_event_t event) +{ + ngx_event_t *ev = event; + + if (ev->timer_set) { + ngx_del_timer(ev); + } +} + + +static void +ngx_http_js_timer_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_js_event_t *js_event; + + js_event = (ngx_http_js_event_t *) ev->data; + + r = js_event->request; + + c = r->connection; + + ngx_http_js_handle_event(r, js_event->vm_event); + + ngx_http_run_posted_requests(c); +} + + +static void +ngx_http_js_handle_event(ngx_http_request_t *r, njs_vm_event_t vm_event) +{ + njs_ret_t rc; + nxt_str_t exception; + ngx_http_js_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); + + njs_vm_post_event(ctx->vm, vm_event); + + rc = njs_vm_run(ctx->vm); + + if (rc == NJS_ERROR) { + njs_vm_retval_to_ext_string(ctx->vm, &exception); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "js exception: %*s", exception.length, exception.start); + + ngx_http_finalize_request(r, NGX_ERROR); + } + + if (rc == NJS_OK) { + ngx_http_post_request(r, NULL); + } +} + + static char * ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -1235,6 +1463,7 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_memzero(&options, sizeof(njs_vm_opt_t)); options.backtrace = 1; + options.ops = &ngx_http_js_ops; jlcf->vm = njs_vm_create(&options); if (jlcf->vm == NULL) { @@ -1339,7 +1568,7 @@ ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) jlcf->content = value[1]; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); - clcf->handler = ngx_http_js_handler; + clcf->handler = ngx_http_js_content_handler; return NGX_CONF_OK; } diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index f11e1459..4890179c 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -30,6 +30,7 @@ typedef struct { typedef struct { njs_vm_t *vm; + ngx_log_t *log; njs_opaque_value_t arg; ngx_buf_t *buf; ngx_chain_t *free; @@ -49,6 +50,7 @@ static ngx_int_t ngx_stream_js_body_filter(ngx_stream_session_t *s, static ngx_int_t ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s); +static void ngx_stream_js_cleanup_ctx(void *data); static void ngx_stream_js_cleanup_vm(void *data); static njs_ret_t ngx_stream_js_ext_get_remote_address(njs_vm_t *vm, @@ -526,6 +528,7 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, ngx_str_t *fname = (ngx_str_t *) data; ngx_int_t rc; + nxt_int_t pending; nxt_str_t name, value, exception; njs_function_t *func; ngx_stream_js_ctx_t *ctx; @@ -557,6 +560,8 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, return NGX_OK; } + pending = njs_vm_pending(ctx->vm); + if (njs_vm_call(ctx->vm, func, &ctx->arg, 1) != NJS_OK) { njs_vm_retval_to_ext_string(ctx->vm, &exception); @@ -571,6 +576,12 @@ ngx_stream_js_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, return NGX_ERROR; } + if (!pending && njs_vm_pending(ctx->vm)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "async operation inside \"%V\" variable handler", fname); + return NGX_ERROR; + } + v->len = value.length; v->valid = 1; v->no_cacheable = 0; @@ -585,6 +596,7 @@ static ngx_int_t ngx_stream_js_init_vm(ngx_stream_session_t *s) { nxt_int_t rc; + nxt_str_t exception; ngx_pool_cleanup_t *cln; ngx_stream_js_ctx_t *ctx; ngx_stream_js_srv_conf_t *jscf; @@ -619,10 +631,17 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s) return NGX_ERROR; } - cln->handler = ngx_stream_js_cleanup_vm; - cln->data = ctx->vm; + ctx->log = s->connection->log; + + cln->handler = ngx_stream_js_cleanup_ctx; + cln->data = ctx; + + if (njs_vm_run(ctx->vm) == NJS_ERROR) { + njs_vm_retval_to_ext_string(ctx->vm, &exception); + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "js exception: %*s", exception.length, exception.start); - if (njs_vm_run(ctx->vm) != NJS_OK) { return NGX_ERROR; } @@ -635,6 +654,19 @@ ngx_stream_js_init_vm(ngx_stream_session_t *s) } +static void +ngx_stream_js_cleanup_ctx(void *data) +{ + ngx_stream_js_ctx_t *ctx = data; + + if (njs_vm_pending(ctx->vm)) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "pending events"); + } + + njs_vm_destroy(ctx->vm); +} + + static void ngx_stream_js_cleanup_vm(void *data) { diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 01c948f3..9c9a7bfb 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -113,7 +114,9 @@ const njs_object_init_t *njs_function_init[] = { &njs_encode_uri_component_function_init, &njs_decode_uri_function_init, &njs_decode_uri_component_function_init, - &njs_require_function_init + &njs_require_function_init, + &njs_set_timeout_function_init, + &njs_clear_timeout_function_init }; @@ -131,6 +134,9 @@ const njs_function_init_t njs_native_functions[] = { { njs_string_decode_uri, { NJS_SKIP_ARG, NJS_STRING_ARG } }, { njs_string_decode_uri_component, { NJS_SKIP_ARG, NJS_STRING_ARG } }, { njs_module_require, { NJS_SKIP_ARG, NJS_STRING_ARG } }, + { njs_set_timeout, + { NJS_SKIP_ARG, NJS_FUNCTION_ARG, NJS_NUMBER_ARG } }, + { njs_clear_timeout, { NJS_SKIP_ARG, NJS_NUMBER_ARG } }, }; diff --git a/njs/njs_event.c b/njs/njs_event.c new file mode 100644 index 00000000..2ac90d37 --- /dev/null +++ b/njs/njs_event.c @@ -0,0 +1,121 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static nxt_int_t njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data); + + +const nxt_lvlhsh_proto_t njs_event_hash_proto + nxt_aligned(64) = +{ + NXT_LVLHSH_DEFAULT, + 0, + njs_event_hash_test, + njs_lvlhsh_alloc, + njs_lvlhsh_free, +}; + + +static nxt_int_t +njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + nxt_str_t id; + njs_event_t *event; + + event = data; + + njs_string_get(&event->id, &id); + + if (nxt_strstr_eq(&lhq->key, &id)) { + return NXT_OK; + } + + return NXT_DECLINED; +} + + +nxt_int_t +njs_add_event(njs_vm_t *vm, njs_event_t *event) +{ + size_t size; + nxt_int_t ret; + nxt_lvlhsh_query_t lhq; + + size = snprintf((char *) njs_string_short_start(&event->id), + NJS_STRING_SHORT, "%u", vm->event_id++); + njs_string_short_set(&event->id, size, size); + + njs_string_get(&event->id, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.value = event; + lhq.proto = &njs_event_hash_proto; + lhq.pool = vm->mem_cache_pool; + + ret = nxt_lvlhsh_insert(&vm->events_hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + njs_exception_internal_error(vm, "Failed to add event with id: %s", + njs_string_short_start(&event->id)); + + njs_del_event(vm, event, NJS_EVENT_RELEASE | NJS_EVENT_DELETE); + return NJS_ERROR; + } + + njs_value_number_set(&vm->retval, vm->event_id - 1); + + return NJS_OK; +} + + +void +njs_del_event(njs_vm_t *vm, njs_event_t *ev, nxt_uint_t action) +{ + nxt_lvlhsh_query_t lhq; + + if (action & NJS_EVENT_RELEASE) { + if (ev->destructor != NULL && ev->host_event != NULL) { + ev->destructor(vm->external, ev->host_event); + } + + ev->host_event = NULL; + } + + if (action & NJS_EVENT_DELETE) { + njs_string_get(&ev->id, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.proto = &njs_event_hash_proto; + lhq.pool = vm->mem_cache_pool; + + if (ev->posted) { + ev->posted = 0; + nxt_queue_remove(&ev->link); + } + + (void) nxt_lvlhsh_delete(&vm->events_hash, &lhq); + } +} diff --git a/njs/njs_event.h b/njs/njs_event.h new file mode 100644 index 00000000..ebfd8357 --- /dev/null +++ b/njs/njs_event.h @@ -0,0 +1,39 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_EVENT_H_INCLUDED_ +#define _NJS_EVENT_H_INCLUDED_ + + +#define NJS_EVENT_RELEASE 1 +#define NJS_EVENT_DELETE 2 + + +#define njs_is_pending_events(vm) (!nxt_lvlhsh_is_empty(&(vm)->events_hash)) + + +typedef struct { + njs_function_t *function; + njs_opaque_value_t *args; + nxt_uint_t nargs; + njs_host_event_t host_event; + njs_event_destructor destructor; + + njs_value_t id; + nxt_queue_link_t link; + + unsigned posted:1; +} njs_event_t; + + +nxt_int_t njs_add_event(njs_vm_t *vm, njs_event_t *event); +void njs_del_event(njs_vm_t *vm, njs_event_t *event, nxt_uint_t action); + + +extern const nxt_lvlhsh_proto_t njs_event_hash_proto; + + +#endif /* _NJS_EVENT_H_INCLUDED_ */ diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 670580b3..57be2d50 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -320,6 +320,8 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) case NJS_TOKEN_DECODE_URI: case NJS_TOKEN_DECODE_URI_COMPONENT: case NJS_TOKEN_REQUIRE: + case NJS_TOKEN_SET_TIMEOUT: + case NJS_TOKEN_CLEAR_TIMEOUT: return njs_generate_builtin_object(vm, parser, node); case NJS_TOKEN_FUNCTION: diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c index 88bb0f30..87236955 100644 --- a/njs/njs_lexer_keyword.c +++ b/njs/njs_lexer_keyword.c @@ -102,6 +102,8 @@ static const njs_keyword_t njs_keywords[] = { { nxt_string("decodeURI"), NJS_TOKEN_DECODE_URI, 0 }, { nxt_string("decodeURIComponent"), NJS_TOKEN_DECODE_URI_COMPONENT, 0 }, { nxt_string("require"), NJS_TOKEN_REQUIRE, 0 }, + { nxt_string("setTimeout"), NJS_TOKEN_SET_TIMEOUT, 0 }, + { nxt_string("clearTimeout"), NJS_TOKEN_CLEAR_TIMEOUT, 0 }, /* Reserved words. */ diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 5083a314..77cbccf7 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -2072,6 +2072,8 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) case NJS_TOKEN_DECODE_URI: case NJS_TOKEN_DECODE_URI_COMPONENT: case NJS_TOKEN_REQUIRE: + case NJS_TOKEN_SET_TIMEOUT: + case NJS_TOKEN_CLEAR_TIMEOUT: return njs_parser_builtin_function(vm, parser, node); default: diff --git a/njs/njs_parser.h b/njs/njs_parser.h index f9fa8f54..c2a33db4 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -199,6 +199,8 @@ typedef enum { NJS_TOKEN_DECODE_URI, NJS_TOKEN_DECODE_URI_COMPONENT, NJS_TOKEN_REQUIRE, + NJS_TOKEN_SET_TIMEOUT, + NJS_TOKEN_CLEAR_TIMEOUT, NJS_TOKEN_RESERVED, } njs_token_t; diff --git a/njs/njs_time.c b/njs/njs_time.c new file mode 100644 index 00000000..40afefed --- /dev/null +++ b/njs/njs_time.c @@ -0,0 +1,141 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +njs_ret_t +njs_set_timeout(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + uint64_t delay; + njs_event_t *event; + njs_vm_ops_t *ops; + + if (nxt_slow_path(nargs < 2)) { + njs_exception_type_error(vm, "too few arguments", NULL); + return NJS_ERROR; + } + + if (nxt_slow_path(!njs_is_function(&args[1]))) { + njs_exception_type_error(vm, "first arg must be a function", NULL); + return NJS_ERROR; + } + + ops = vm->ops; + if (nxt_slow_path(ops == NULL)) { + njs_exception_internal_error(vm, "not supported by host environment", + NULL); + return NJS_ERROR; + } + + delay = 0; + + if (nargs >= 3 && njs_is_number(&args[2])) { + delay = args[2].data.u.number; + } + + event = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_event_t)); + if (nxt_slow_path(event == NULL)) { + goto memory_error; + } + + event->destructor = ops->clear_timer; + event->function = args[1].data.u.function; + event->nargs = (nargs >= 3) ? nargs - 3 : 0; + event->posted = 0; + + if (event->nargs != 0) { + event->args = nxt_mem_cache_alloc(vm->mem_cache_pool, + sizeof(njs_value_t) * event->nargs); + if (nxt_slow_path(event->args == NULL)) { + goto memory_error; + } + + memcpy(event->args, &args[3], sizeof(njs_value_t) * event->nargs); + } + + event->host_event = ops->set_timer(vm->external, delay, event); + if (event->host_event == NULL) { + njs_exception_internal_error(vm, "set_timer() failed", NULL); + return NJS_ERROR; + } + + return njs_add_event(vm, event); + +memory_error: + + njs_exception_memory_error(vm); + return NJS_ERROR; +} + + +njs_ret_t +njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + u_char buf[16]; + njs_ret_t ret; + njs_event_t *event; + nxt_lvlhsh_query_t lhq; + + if (nxt_fast_path(nargs < 2) || !njs_is_number(&args[1])) { + vm->retval = njs_string_void; + return NJS_OK; + } + + lhq.key.start = buf; + lhq.key.length = snprintf((char *) buf, sizeof(buf) - 1, "%u", + (unsigned) args[1].data.u.number); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.proto = &njs_event_hash_proto; + lhq.pool = vm->mem_cache_pool; + + ret = nxt_lvlhsh_find(&vm->events_hash, &lhq); + if (ret == NXT_OK) { + event = lhq.value; + njs_del_event(vm, event, NJS_EVENT_RELEASE | NJS_EVENT_DELETE); + } + + vm->retval = njs_string_void; + + return NJS_OK; +} + + +const njs_object_init_t njs_set_timeout_function_init = { + nxt_string("setTimeout"), + NULL, + 0, +}; + + +const njs_object_init_t njs_clear_timeout_function_init = { + nxt_string("clearTimeout"), + NULL, + 0, +}; diff --git a/njs/njs_time.h b/njs/njs_time.h new file mode 100644 index 00000000..86185be5 --- /dev/null +++ b/njs/njs_time.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_TIMEOUT_H_INCLUDED_ +#define _NJS_TIMEOUT_H_INCLUDED_ + + +njs_ret_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); +njs_ret_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused); + + +extern const njs_object_init_t njs_set_timeout_function_init; +extern const njs_object_init_t njs_clear_timeout_function_init; + +#endif /* _NJS_TIMEOUT_H_INCLUDED_ */ diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 58c9b055..38415a6d 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -881,7 +881,9 @@ enum njs_function_e { NJS_FUNCTION_STRING_DECODE_URI, NJS_FUNCTION_STRING_DECODE_URI_COMPONENT, NJS_FUNCTION_REQUIRE, -#define NJS_FUNCTION_MAX (NJS_FUNCTION_REQUIRE + 1) + NJS_FUNCTION_SET_TIMEOUT, + NJS_FUNCTION_CLEAR_TIMEOUT, +#define NJS_FUNCTION_MAX (NJS_FUNCTION_CLEAR_TIMEOUT + 1) }; @@ -960,12 +962,12 @@ struct njs_vm_s { njs_value_t *scopes[NJS_SCOPES]; - void *external; + njs_external_ptr_t external; njs_native_frame_t *top_frame; njs_frame_t *active_frame; - nxt_array_t *external_objects; /* of void * */ + nxt_array_t *external_objects; /* of njs_external_ptr_t */ nxt_lvlhsh_t externals_hash; nxt_lvlhsh_t external_prototypes_hash; @@ -974,6 +976,12 @@ struct njs_vm_s { nxt_lvlhsh_t values_hash; nxt_lvlhsh_t modules_hash; + uint32_t event_id; + nxt_lvlhsh_t events_hash; + nxt_queue_t posted_events; + + njs_vm_ops_t *ops; + /* * The prototypes and constructors arrays must be together because * they are copied from njs_vm_shared_t by single memcpy() diff --git a/njs/njscript.c b/njs/njscript.c index b86874c5..9d0980fd 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -20,13 +20,17 @@ #include #include #include +#include #include #include #include +#include +#include #include static nxt_int_t njs_vm_init(njs_vm_t *vm); +static nxt_int_t njs_vm_handle_events(njs_vm_t *vm); static void * @@ -176,6 +180,8 @@ njs_vm_create(njs_vm_opt_t *options) nxt_lvlhsh_init(&vm->externals_hash); nxt_lvlhsh_init(&vm->external_prototypes_hash); + vm->ops = options->ops; + vm->trace.level = NXT_LEVEL_TRACE; vm->trace.size = 2048; vm->trace.handler = njs_parser_trace_handler; @@ -212,6 +218,23 @@ njs_vm_create(njs_vm_opt_t *options) void njs_vm_destroy(njs_vm_t *vm) { + njs_event_t *event; + nxt_lvlhsh_each_t lhe; + + if (njs_is_pending_events(vm)) { + nxt_lvlhsh_each_init(&lhe, &njs_event_hash_proto); + + for ( ;; ) { + event = nxt_lvlhsh_each(&vm->events_hash, &lhe); + + if (event == NULL) { + break; + } + + njs_del_event(vm, event, NJS_EVENT_RELEASE); + } + } + nxt_mem_cache_pool_destroy(vm->mem_cache_pool); } @@ -294,7 +317,7 @@ fail: njs_vm_t * -njs_vm_clone(njs_vm_t *vm, void *external) +njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external) { njs_vm_t *nvm; uint32_t items; @@ -341,6 +364,8 @@ njs_vm_clone(njs_vm_t *vm, void *external) vm->external_objects->items = items; } + nvm->ops = vm->ops; + nvm->current = vm->current; nvm->external = external; @@ -413,6 +438,9 @@ njs_vm_init(njs_vm_t *vm) return NXT_ERROR; } + nxt_lvlhsh_init(&vm->events_hash); + nxt_queue_init(&vm->posted_events); + if (vm->debug != NULL) { backtrace = nxt_array_create(4, sizeof(njs_backtrace_entry_t), &njs_array_mem_proto, vm->mem_cache_pool); @@ -475,6 +503,29 @@ njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args, } +nxt_int_t +njs_vm_pending(njs_vm_t *vm) +{ + return njs_is_pending_events(vm); +} + + +nxt_int_t +njs_vm_post_event(njs_vm_t *vm, njs_vm_event_t vm_event) +{ + njs_event_t *event; + + event = (njs_event_t *) vm_event; + + if (!event->posted) { + event->posted = 1; + nxt_queue_insert_tail(&vm->posted_events, &event->link); + } + + return NJS_OK; +} + + nxt_int_t njs_vm_run(njs_vm_t *vm) { @@ -489,6 +540,10 @@ njs_vm_run(njs_vm_t *vm) ret = njs_vmcode_interpreter(vm); + if (ret == NJS_STOP) { + ret = njs_vm_handle_events(vm); + } + if (nxt_slow_path(ret == NXT_AGAIN)) { nxt_thread_log_debug("VM: AGAIN"); return ret; @@ -528,6 +583,38 @@ njs_vm_run(njs_vm_t *vm) } +static nxt_int_t +njs_vm_handle_events(njs_vm_t *vm) +{ + nxt_int_t ret; + njs_event_t *ev; + nxt_queue_t *events; + nxt_queue_link_t *link; + + events = &vm->posted_events; + + for ( ;; ) { + link = nxt_queue_first(events); + + if (link == nxt_queue_tail(events)) { + break; + } + + ev = nxt_queue_link_data(link, njs_event_t, link); + + njs_del_event(vm, ev, NJS_EVENT_DELETE); + + ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs); + + if (ret == NJS_ERROR) { + return ret; + } + } + + return njs_is_pending_events(vm) ? NJS_AGAIN : NJS_STOP; +} + + nxt_noinline njs_value_t * njs_vm_retval(njs_vm_t *vm) { diff --git a/njs/njscript.h b/njs/njscript.h index 9dd24c92..ad4165af 100644 --- a/njs/njscript.h +++ b/njs/njscript.h @@ -70,9 +70,47 @@ struct njs_external_s { uintptr_t data; }; + +/* + * NJS and event loops. + * + * njs_vm_ops_t callbacks are used to interact with the event loop environment. + * + * Functions get an external object as the first argument. The external + * object is provided as the third argument to njs_vm_clone(). + * + * The callbacks are expected to return to the VM the unique id of an + * underlying event. This id will be passed as the second argument to + * njs_event_destructor() at the moment the VM wants to destroy it. + * + * When an underlying events fires njs_vm_post_event() should be invoked with + * the value provided as vm_event. + * + * The events posted by njs_vm_post_event() are processed as soon as + * njs_vm_run() is invoked. njs_vm_run() returns NJS_AGAIN until pending events + * are present. + */ + +typedef void * njs_vm_event_t; +typedef void * njs_host_event_t; +typedef void * njs_external_ptr_t; + +typedef njs_host_event_t (*njs_set_timer)(njs_external_ptr_t external, + uint64_t delay, njs_vm_event_t vm_event); +typedef void (*njs_event_destructor)(njs_external_ptr_t external, + njs_host_event_t event); + + +typedef struct { + njs_set_timer set_timer; + njs_event_destructor clear_timer; +} njs_vm_ops_t; + + typedef struct { - void *external; + njs_external_ptr_t external; njs_vm_shared_t *shared; + njs_vm_ops_t *ops; uint8_t trailer; /* 1 bit */ uint8_t accumulative; /* 1 bit */ @@ -97,9 +135,13 @@ NXT_EXPORT njs_vm_t *njs_vm_create(njs_vm_opt_t *options); NXT_EXPORT void njs_vm_destroy(njs_vm_t *vm); NXT_EXPORT nxt_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end); -NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, void *external); +NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external); NXT_EXPORT nxt_int_t njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args, nxt_uint_t nargs); + +NXT_EXPORT nxt_int_t njs_vm_pending(njs_vm_t *vm); +NXT_EXPORT nxt_int_t njs_vm_post_event(njs_vm_t *vm, njs_vm_event_t vm_event); + NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t *vm); NXT_EXPORT const njs_extern_t *njs_vm_external_prototype(njs_vm_t *vm, diff --git a/njs/test/njs_interactive_test.c b/njs/test/njs_interactive_test.c index b09bccac..55fe25b0 100644 --- a/njs/test/njs_interactive_test.c +++ b/njs/test/njs_interactive_test.c @@ -172,6 +172,11 @@ static njs_interactive_test_t njs_test[] = " at require (native)\n" " at main (native)\n") }, + { nxt_string("setTimeout()" ENTER), + nxt_string("TypeError: too few arguments\n" + " at setTimeout (native)\n" + " at main (native)\n") }, + { nxt_string("function f(o) {function f_in(o) {return o.a.a};" " return f_in(o)}; f({})" ENTER), nxt_string("TypeError: cannot get property 'a' of undefined\n" diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 1b457ba4..f451ef9e 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -9003,6 +9003,25 @@ static njs_unit_test_t njs_test[] = "fs.writeFileSync('/njs_unknown_path', '', true)"), nxt_string("TypeError: Unknown options type (a string or object required)") }, + /* setTimeout(). */ + + { nxt_string("setTimeout()"), + nxt_string("TypeError: too few arguments") }, + + { nxt_string("setTimeout(function(){})"), + nxt_string("InternalError: not supported by host environment") }, + + { nxt_string("setTimeout(function(){}, 12)"), + nxt_string("InternalError: not supported by host environment") }, + + /* clearTimeout(). */ + + { nxt_string("clearTimeout()"), + nxt_string("undefined") }, + + { nxt_string("clearTimeout(123)"), + nxt_string("undefined") }, + /* Trick: number to boolean. */ { nxt_string("var a = 0; !!a"),