details: https://hg.nginx.org/njs/rev/e4297a78844e branches: changeset: 1976:e4297a78844e user: Dmitry Volyntsev <xei...@nginx.com> date: Mon Oct 10 18:45:09 2022 -0700 description: Refactored bound function calls according to the spec.
This fixes #533, #546, #579 issues on Github. diffstat: src/njs_disassembler.c | 14 +--- src/njs_error.c | 9 +- src/njs_extern.c | 1 - src/njs_function.c | 171 ++++++++++++++++++++-------------------------- src/njs_function.h | 5 +- src/njs_generator.c | 10 +- src/njs_promise.c | 1 - src/njs_value.h | 6 +- src/njs_vm.c | 1 - src/njs_vmcode.c | 63 +++++++++++----- src/njs_vmcode.h | 9 +-- src/test/njs_unit_test.c | 18 +++- test/js/async_bind.t.js | 13 +++ 13 files changed, 161 insertions(+), 160 deletions(-) diffs (735 lines): diff -r 16442fa970ee -r e4297a78844e src/njs_disassembler.c --- a/src/njs_disassembler.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_disassembler.c Mon Oct 10 18:45:09 2022 -0700 @@ -17,6 +17,8 @@ typedef struct { static njs_code_name_t code_names[] = { + { NJS_VMCODE_PUT_ARG, sizeof(njs_vmcode_1addr_t), + njs_str("PUT ARG ") }, { NJS_VMCODE_OBJECT, sizeof(njs_vmcode_object_t), njs_str("OBJECT ") }, { NJS_VMCODE_FUNCTION, sizeof(njs_vmcode_function_t), @@ -204,7 +206,6 @@ njs_disassemble(u_char *start, u_char *e njs_vmcode_import_t *import; njs_vmcode_finally_t *finally; njs_vmcode_try_end_t *try_end; - njs_vmcode_move_arg_t *move_arg; njs_vmcode_try_start_t *try_start; njs_vmcode_operation_t operation; njs_vmcode_cond_jump_t *cond_jump; @@ -513,17 +514,6 @@ njs_disassemble(u_char *start, u_char *e continue; } - if (operation == NJS_VMCODE_MOVE_ARG) { - move_arg = (njs_vmcode_move_arg_t *) p; - - njs_printf("%5uD | %05uz MOVE ARGUMENT %uD %04Xz\n", - line, p - start, move_arg->dst, (size_t) move_arg->src); - - p += sizeof(njs_vmcode_move_arg_t); - - continue; - } - code_name = code_names; n = njs_nitems(code_names); diff -r 16442fa970ee -r e4297a78844e src/njs_error.c --- a/src/njs_error.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_error.c Mon Oct 10 18:45:09 2022 -0700 @@ -1292,6 +1292,11 @@ njs_add_backtrace_entry(njs_vm_t *vm, nj function = native_frame->function; + if (function != NULL && function->bound != NULL) { + /* Skip. */ + return NJS_OK; + } + be = njs_arr_add(stack); if (njs_slow_path(be == NULL)) { return NJS_ERROR; @@ -1301,10 +1306,6 @@ njs_add_backtrace_entry(njs_vm_t *vm, nj be->file = njs_str_value(""); if (function != NULL && function->native) { - while (function->bound != NULL) { - function = function->u.bound_target; - } - ret = njs_builtin_match_native_function(vm, function, &be->name); if (ret == NJS_OK) { return NJS_OK; diff -r 16442fa970ee -r e4297a78844e src/njs_extern.c --- a/src/njs_extern.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_extern.c Mon Oct 10 18:45:09 2022 -0700 @@ -77,7 +77,6 @@ njs_external_add(njs_vm_t *vm, njs_arr_t function->object.type = NJS_FUNCTION; function->object.shared = 1; function->object.extensible = 1; - function->args_offset = 1; function->native = 1; function->u.native = external->u.method.native; function->magic8 = external->u.method.magic8; diff -r 16442fa970ee -r e4297a78844e src/njs_function.c --- a/src/njs_function.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_function.c Mon Oct 10 18:45:09 2022 -0700 @@ -30,7 +30,6 @@ njs_function_alloc(njs_vm_t *vm, njs_fun */ function->ctor = lambda->ctor; - function->args_offset = 1; function->u.lambda = lambda; if (function->ctor) { @@ -77,7 +76,6 @@ njs_vm_function_alloc(njs_vm_t *vm, njs_ } function->native = 1; - function->args_offset = 1; function->u.native = native; return function; @@ -376,12 +374,10 @@ njs_function_native_frame(njs_vm_t *vm, njs_bool_t ctor) { size_t size; - njs_uint_t n; - njs_value_t *value, *bound; + njs_value_t *value; njs_native_frame_t *frame; - size = NJS_NATIVE_FRAME_SIZE - + (function->args_offset + nargs) * sizeof(njs_value_t); + size = NJS_NATIVE_FRAME_SIZE + (1 /* this */ + nargs) * sizeof(njs_value_t); frame = njs_function_frame_alloc(vm, size); if (njs_slow_path(frame == NULL)) { @@ -389,31 +385,16 @@ njs_function_native_frame(njs_vm_t *vm, } frame->function = function; - frame->nargs = function->args_offset + nargs; + frame->nargs = nargs; frame->ctor = ctor; frame->native = 1; frame->pc = NULL; value = (njs_value_t *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE); - frame->arguments = value; - frame->arguments_offset = value + function->args_offset; - - bound = function->bound; - - if (bound == NULL) { - /* GC: njs_retain(this); */ - *value++ = *this; + njs_value_assign(value++, this++); - } else { - n = function->args_offset; - - do { - /* GC: njs_retain(bound); */ - *value++ = *bound++; - n--; - } while (n != 0); - } + frame->arguments = value; if (args != NULL) { memcpy(value, args, nargs * sizeof(njs_value_t)); @@ -430,37 +411,15 @@ njs_function_lambda_frame(njs_vm_t *vm, { size_t n, frame_size; uint32_t args_count, value_count, value_size; - njs_value_t *value, *bound, **new; + njs_value_t *value, **new; njs_frame_t *frame; - njs_function_t *target; njs_native_frame_t *native_frame; njs_function_lambda_t *lambda; - bound = function->bound; - - if (njs_fast_path(bound == NULL)) { - lambda = function->u.lambda; - target = function; - - } else { - target = function->u.bound_target; - - if (njs_slow_path(target->bound != NULL)) { + lambda = function->u.lambda; - /* - * FIXME: bound functions should call target function with - * bound "this" and bound args. - */ - - njs_internal_error(vm, "chain of bound function are not supported"); - return NJS_ERROR; - } - - lambda = target->u.lambda; - } - - args_count = function->args_offset + njs_max(nargs, lambda->nargs); - value_count = args_count + njs_max(args_count, lambda->nlocal); + args_count = njs_max(nargs, lambda->nargs); + value_count = args_count + lambda->nlocal; value_size = value_count * sizeof(njs_value_t *); @@ -485,9 +444,8 @@ njs_function_lambda_frame(njs_vm_t *vm, } native_frame->arguments = value; - native_frame->arguments_offset = value + (function->args_offset - 1); native_frame->local = new + args_count; - native_frame->function = target; + native_frame->function = function; native_frame->nargs = nargs; native_frame->ctor = ctor; native_frame->native = 0; @@ -502,28 +460,11 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_set_object(native_frame->local[0], &vm->global_object); } - if (bound != NULL) { - n = function->args_offset; - native_frame->nargs += n - 1; - - if (!ctor) { - *native_frame->local[0] = *bound; - } - - bound++; - n--; - - while (n != 0) { - *value++ = *bound++; - n--; - }; - } - /* Copy arguments. */ if (args != NULL) { while (nargs != 0) { - *value++ = *args++; + njs_value_assign(value++, args++); nargs--; } } @@ -624,7 +565,7 @@ njs_function_lambda_call(njs_vm_t *vm, v lambda = function->u.lambda; args = vm->top_frame->arguments; - local = vm->top_frame->local + function->args_offset; + local = vm->top_frame->local + 1 /* this */; /* Move all arguments. */ @@ -702,7 +643,7 @@ njs_int_t njs_function_native_call(njs_vm_t *vm) { njs_int_t ret; - njs_function_t *function, *target; + njs_function_t *function; njs_native_frame_t *native, *previous; njs_function_native_t call; @@ -723,21 +664,10 @@ njs_function_native_call(njs_vm_t *vm) } #endif - if (njs_fast_path(function->bound == NULL)) { - call = function->u.native; - - } else { - target = function->u.bound_target; + call = function->u.native; - if (njs_slow_path(target->bound != NULL)) { - njs_internal_error(vm, "chain of bound function are not supported"); - return NJS_ERROR; - } - - call = target->u.native; - } - - ret = call(vm, native->arguments, native->nargs, function->magic8); + ret = call(vm, &native->arguments[-1], 1 /* this */ + native->nargs, + function->magic8); #ifdef NJS_DEBUG_OPCODE if (vm->options.opcode_debug) { @@ -833,14 +763,13 @@ njs_function_frame_save(njs_vm_t *vm, nj function = active->function; lambda = function->u.lambda; - args_count = function->args_offset + njs_max(native->nargs, lambda->nargs); - value_count = args_count + njs_max(args_count, lambda->nlocal); + args_count = njs_max(native->nargs, lambda->nargs); + value_count = args_count + lambda->nlocal; new = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); value = (njs_value_t *) (new + value_count); native->arguments = value; - native->arguments_offset = value + (function->args_offset - 1); native->local = new + njs_function_frame_args_count(active); native->pc = pc; @@ -848,14 +777,14 @@ njs_function_frame_save(njs_vm_t *vm, nj p = native->arguments; while (start < end) { - *p = *start++; + njs_value_assign(p, start++); *new++ = p++; } /* Move all arguments. */ p = native->arguments; - local = native->local + function->args_offset; + local = native->local + 1 /* this */; for (n = 0; n < function->args_count; n++) { if (!njs_is_valid(p)) { @@ -1461,11 +1390,54 @@ activate: static njs_int_t +njs_function_bound_call(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + u_char *p; + njs_int_t ret; + size_t args_count; + njs_value_t *arguments; + njs_function_t *function, *bound; + + function = vm->top_frame->function; + bound = function->context; + + njs_assert(bound != NULL); + + args_count = 1 /* this */ + function->bound_args; + + if (nargs == 1) { + return njs_function_apply(vm, bound, function->bound, args_count, + &vm->retval); + } + + arguments = njs_mp_alloc(vm->mem_pool, + (args_count + nargs - 1) * sizeof(njs_value_t)); + if (njs_slow_path(arguments == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + p = njs_cpymem(arguments, function->bound, + args_count * sizeof(njs_value_t)); + memcpy(p, &args[1], (nargs - 1) * sizeof(njs_value_t)); + + ret = njs_function_apply(vm, bound, arguments, args_count + nargs - 1, + &vm->retval); + + njs_mp_free(vm->mem_pool, arguments); + + return ret; +} + + +static njs_int_t njs_function_prototype_bind(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { size_t size; njs_int_t ret; + njs_uint_t bound_args; njs_value_t *values, name; njs_function_t *function; @@ -1481,6 +1453,8 @@ njs_function_prototype_bind(njs_vm_t *vm } *function = *njs_function(&args[0]); + function->native = 1; + function->u.native = njs_function_bound_call; njs_lvlhsh_init(&function->object.hash); @@ -1490,7 +1464,7 @@ njs_function_prototype_bind(njs_vm_t *vm function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; function->object.shared = 0; - function->u.bound_target = njs_function(&args[0]); + function->context = njs_function(&args[0]); ret = njs_value_property(vm, &args[0], njs_value_arg(&njs_string_name), &name); @@ -1509,21 +1483,23 @@ njs_function_prototype_bind(njs_vm_t *vm if (nargs == 1) { args = njs_value_arg(&njs_value_undefined); + bound_args = 0; } else { - nargs--; args++; + bound_args = nargs - 2; } - if (nargs > function->args_count) { + if (bound_args > function->args_count) { function->args_count = 0; } else { - function->args_count -= nargs - 1; + function->args_count -= bound_args; } - function->args_offset = nargs; - size = nargs * sizeof(njs_value_t); + function->bound_args = bound_args; + + size = (1 /* this */ + bound_args) * sizeof(njs_value_t); values = njs_mp_alloc(vm->mem_pool, size); if (njs_slow_path(values == NULL)) { @@ -1700,7 +1676,6 @@ const njs_object_type_init_t njs_functi .constructor_props = &njs_function_constructor_init, .prototype_props = &njs_function_prototype_init, .prototype_value = { .function = { .native = 1, - .args_offset = 1, .u.native = njs_prototype_function, .object = { .type = NJS_FUNCTION } } }, }; diff -r 16442fa970ee -r e4297a78844e src/njs_function.h --- a/src/njs_function.h Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_function.h Mon Oct 10 18:45:09 2022 -0700 @@ -47,9 +47,9 @@ struct njs_native_frame_s { njs_function_t *function; njs_native_frame_t *previous; + /* Points to the first arg after 'this'. */ njs_value_t *arguments; njs_object_t *arguments_object; - njs_value_t *arguments_offset; njs_value_t **local; uint32_t size; @@ -57,7 +57,10 @@ struct njs_native_frame_s { njs_value_t *retval; + /* Number of allocated args on the frame. */ uint32_t nargs; + /* Number of already put args. */ + uint32_t put_args; uint8_t native; /* 1 bit */ /* Function is called as constructor with "new" keyword. */ diff -r 16442fa970ee -r e4297a78844e src/njs_generator.c --- a/src/njs_generator.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_generator.c Mon Oct 10 18:45:09 2022 -0700 @@ -4382,22 +4382,20 @@ njs_generate_move_arguments(njs_vm_t *vm njs_parser_node_t *node) { njs_jump_off_t func_offset; - njs_vmcode_move_arg_t *move_arg; + njs_vmcode_1addr_t *put_arg; njs_vmcode_function_frame_t *func; if (node == NULL) { return njs_generator_stack_pop(vm, generator, generator->context); } - njs_generate_code(generator, njs_vmcode_move_arg_t, move_arg, - NJS_VMCODE_MOVE_ARG, 0, node); - move_arg->src = node->left->index; + njs_generate_code(generator, njs_vmcode_1addr_t, put_arg, + NJS_VMCODE_PUT_ARG, 0, node); + put_arg->index = node->left->index; func_offset = *((njs_jump_off_t *) generator->context); func = njs_code_ptr(generator, njs_vmcode_function_frame_t, func_offset); - move_arg->dst = (njs_uint_t) func->nargs; - func->nargs++; if (node->right == NULL) { diff -r 16442fa970ee -r e4297a78844e src/njs_promise.c --- a/src/njs_promise.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_promise.c Mon Oct 10 18:45:09 2022 -0700 @@ -255,7 +255,6 @@ njs_promise_create_function(njs_vm_t *vm function->object.shared_hash = vm->shared->arrow_instance_hash; function->object.type = NJS_FUNCTION; function->object.extensible = 1; - function->args_offset = 1; function->native = 1; function->context = context; diff -r 16442fa970ee -r e4297a78844e src/njs_value.h --- a/src/njs_value.h Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_value.h Mon Oct 10 18:45:09 2022 -0700 @@ -250,7 +250,8 @@ struct njs_typed_array_s { struct njs_function_s { njs_object_t object; - uint8_t args_offset; + /* Number of bound args excluding 'this'. */ + uint8_t bound_args; uint8_t args_count:4; @@ -265,11 +266,11 @@ struct njs_function_s { union { njs_function_lambda_t *lambda; njs_function_native_t native; - njs_function_t *bound_target; } u; void *context; + /* Bound args including 'this'. */ njs_value_t *bound; }; @@ -428,7 +429,6 @@ typedef struct { .magic8 = _magic, \ .args_count = _args_count, \ .ctor = _ctor, \ - .args_offset = 1, \ .u.native = _function, \ .object = { .type = NJS_FUNCTION, \ .shared = 1, \ diff -r 16442fa970ee -r e4297a78844e src/njs_vm.c --- a/src/njs_vm.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_vm.c Mon Oct 10 18:45:09 2022 -0700 @@ -300,7 +300,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_ lambda->declarations = (arr != NULL) ? arr->start : NULL; lambda->ndeclarations = (arr != NULL) ? arr->items : 0; - module->function.args_offset = 1; module->function.u.lambda = lambda; return module; diff -r 16442fa970ee -r e4297a78844e src/njs_vmcode.c --- a/src/njs_vmcode.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_vmcode.c Mon Oct 10 18:45:09 2022 -0700 @@ -98,6 +98,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_c njs_value_t numeric1, numeric2, primitive1, primitive2; njs_frame_t *frame; njs_jump_off_t ret; + njs_vmcode_1addr_t *put_arg; njs_vmcode_await_t *await; njs_native_frame_t *previous, *native; njs_property_next_t *next; @@ -105,7 +106,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_c njs_vmcode_finally_t *finally; njs_vmcode_generic_t *vmcode; njs_vmcode_variable_t *var; - njs_vmcode_move_arg_t *move_arg; njs_vmcode_prop_get_t *get; njs_vmcode_prop_set_t *set; njs_vmcode_operation_t op; @@ -657,18 +657,16 @@ next: } else { switch (op) { - case NJS_VMCODE_MOVE_ARG: - move_arg = (njs_vmcode_move_arg_t *) pc; + case NJS_VMCODE_PUT_ARG: + put_arg = (njs_vmcode_1addr_t *) pc; native = vm->top_frame; - hint = move_arg->dst; - - value1 = &native->arguments_offset[hint]; - njs_vmcode_operand(vm, move_arg->src, value2); - - *value1 = *value2; - - ret = sizeof(njs_vmcode_move_arg_t); + value1 = &native->arguments[native->put_args++]; + njs_vmcode_operand(vm, put_arg->index, value2); + + njs_value_assign(value1, value2); + + ret = sizeof(njs_vmcode_1addr_t); break; case NJS_VMCODE_STOP: @@ -1290,7 +1288,6 @@ njs_vmcode_template_literal(njs_vm_t *vm static const njs_function_t concat = { .native = 1, - .args_offset = 1, .u.native = njs_string_prototype_concat }; @@ -1584,7 +1581,7 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs function = njs_function(constructor); if (function->bound != NULL) { - function = function->u.bound_target; + function = function->context; njs_set_function(&bound, function); constructor = &bound; } @@ -1849,33 +1846,57 @@ static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, const njs_value_t *this, uintptr_t nargs, njs_bool_t ctor) { - njs_value_t val; + njs_int_t ret; + njs_value_t new_target, *args; njs_object_t *object; - njs_function_t *function; + njs_function_t *function, *target; if (njs_fast_path(njs_is_function(value))) { function = njs_function(value); + target = function; + args = NULL; if (ctor) { - if (!function->ctor) { + if (function->bound != NULL) { + target = function->context; + nargs += function->bound_args; + + args = njs_mp_alloc(vm->mem_pool, nargs * sizeof(njs_value_t)); + if (njs_slow_path(args == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + memcpy(args, &function->bound[1], + function->bound_args * sizeof(njs_value_t)); + } + + if (!target->ctor) { njs_type_error(vm, "%s is not a constructor", njs_type_string(value->type)); return NJS_ERROR; } - if (!function->native) { + if (!target->native) { object = njs_function_new_object(vm, value); if (njs_slow_path(object == NULL)) { return NJS_ERROR; } - njs_set_object(&val, object); - this = &val; + njs_set_object(&new_target, object); + this = &new_target; } } - return njs_function_frame(vm, function, this, NULL, nargs, ctor); + ret = njs_function_frame(vm, target, this, args, nargs, ctor); + + if (args != NULL) { + vm->top_frame->put_args = function->bound_args; + njs_mp_free(vm->mem_pool, args); + } + + return ret; } njs_type_error(vm, "%s is not a function", njs_type_string(value->type)); @@ -1902,7 +1923,7 @@ njs_function_new_object(njs_vm_t *vm, nj function = njs_function(constructor); if (function->bound != NULL) { - function = function->u.bound_target; + function = function->context; njs_set_function(&bound, function); constructor = &bound; } diff -r 16442fa970ee -r e4297a78844e src/njs_vmcode.h --- a/src/njs_vmcode.h Thu Oct 06 18:28:52 2022 -0700 +++ b/src/njs_vmcode.h Mon Oct 10 18:45:09 2022 -0700 @@ -31,7 +31,7 @@ typedef uint8_t enum { - NJS_VMCODE_MOVE_ARG = 0, + NJS_VMCODE_PUT_ARG = 0, NJS_VMCODE_STOP, NJS_VMCODE_JUMP, NJS_VMCODE_PROPERTY_SET, @@ -411,13 +411,6 @@ typedef struct { typedef struct { njs_vmcode_t code; - njs_index_t src; - njs_uint_t dst; -} njs_vmcode_move_arg_t; - - -typedef struct { - njs_vmcode_t code; njs_value_t *function; njs_index_t retval; } njs_vmcode_function_copy_t; diff -r 16442fa970ee -r e4297a78844e src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Thu Oct 06 18:28:52 2022 -0700 +++ b/src/test/njs_unit_test.c Mon Oct 10 18:45:09 2022 -0700 @@ -7818,12 +7818,22 @@ static njs_unit_test_t njs_test[] = { njs_str("var bArray = Array.bind(null, 10); new bArray(16)"), njs_str("10,16") }, -#if 0 /* FIXME: refactor Bound calls (9.4.1.1[[Call]]). */ { njs_str("function f(x,y) {return {args:arguments,length:arguments.length}};" - "var bf = f.bind({}, 'a'); var bbf = bf.bind({},'b'); var o = bbf('c');"), - "[o.args[0], o.args[2], o.length]" + "var bf = f.bind({}, 'a'); var bbf = bf.bind({},'b'); var o = bbf('c');" + "[o.args[0], o.args[2], o.length]"), njs_str("a,c,3") }, -#endif + + { njs_str("var f = function (a, b) {return [this, a, b]};" + "var b1 = f.bind('THIS', 'x');" + "var b2 = b1.bind('WAKA', 'y');" + "njs.dump([f(2,3), b1(3), b2()])"), + njs_str("[[undefined,2,3],['THIS','x',3],['THIS','x','y']]") }, + + { njs_str("var f = Math.max;" + "var b1 = f.bind('THIS', 4);" + "var b2 = b1.bind('WAKA', 5);" + "njs.dump([f(2,3), b1(3), b2()])"), + njs_str("[3,4,5]") }, { njs_str("var s = { toString: function() { return '123' } };" "var a = 'abc'; a.concat('абв', s)"), diff -r 16442fa970ee -r e4297a78844e test/js/async_bind.t.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/js/async_bind.t.js Mon Oct 10 18:45:09 2022 -0700 @@ -0,0 +1,13 @@ +/*--- +includes: [compareArray.js] +flags: [async] +---*/ + +async function f(a1, a2, a3) { + var v = await a1; + return [a1, a2, a3]; +} + +f.bind(null,1,2)('a') +.then(v => assert.compareArray(v, [1, 2, 'a'])) +.then($DONE, $DONE); _______________________________________________ nginx-devel mailing list -- nginx-devel@nginx.org To unsubscribe send an email to nginx-devel-le...@nginx.org