Skip to content

Commit eda6fa7

Browse files
committed
Implemented lazy stack symbolization.
Previously, when an exception was thrown, the exception got 'stack' property attached which contained the backtrace information about where the exception happened. This could be a heavy operation and it was not always needed. To optimize it, the process is split into 2 phases. The first phase collects all the necessary info about the current stack. The second phase, where the stack symbolization happens, occurs only when this property is referenced.
1 parent 6ed6c10 commit eda6fa7

File tree

3 files changed

+150
-45
lines changed

3 files changed

+150
-45
lines changed

src/njs_error.c

Lines changed: 138 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
#include <njs_main.h>
99

1010

11+
typedef struct {
12+
union {
13+
njs_function_t *function;
14+
u_char *pc;
15+
} u;
16+
uint8_t native;
17+
} njs_stack_entry_t;
18+
19+
1120
typedef struct {
1221
njs_str_t name;
1322
njs_str_t file;
@@ -16,7 +25,7 @@ typedef struct {
1625

1726

1827
static njs_int_t njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
19-
njs_native_frame_t *native_frame);
28+
njs_stack_entry_t *se);
2029
static njs_int_t njs_backtrace_to_string(njs_vm_t *vm, njs_arr_t *backtrace,
2130
njs_str_t *dst);
2231

@@ -87,33 +96,34 @@ njs_error_fmt_new(njs_vm_t *vm, njs_value_t *dst, njs_object_type_t type,
8796

8897

8998
static njs_int_t
90-
njs_error_stack_new(njs_vm_t *vm, njs_object_t *error, njs_value_t *retval)
99+
njs_error_stack_new(njs_vm_t *vm, njs_object_value_t *error)
91100
{
92-
njs_int_t ret;
93-
njs_str_t string;
94101
njs_arr_t *stack;
95-
njs_value_t value;
102+
njs_stack_entry_t *se;
96103
njs_native_frame_t *frame;
97104

98-
njs_set_object(&value, error);
99-
100-
ret = njs_error_to_string(vm, retval, &value);
101-
if (njs_slow_path(ret != NJS_OK)) {
102-
return ret;
103-
}
104-
105-
stack = njs_arr_create(vm->mem_pool, 4, sizeof(njs_backtrace_entry_t));
105+
stack = njs_arr_create(vm->mem_pool, 4, sizeof(njs_stack_entry_t));
106106
if (njs_slow_path(stack == NULL)) {
107107
return NJS_ERROR;
108108
}
109109

110110
frame = vm->top_frame;
111111

112112
for ( ;; ) {
113-
if ((frame->native || frame->pc != NULL)
114-
&& njs_add_backtrace_entry(vm, stack, frame) != NJS_OK)
115-
{
116-
break;
113+
if (frame->native || frame->pc != NULL) {
114+
se = njs_arr_add(stack);
115+
if (njs_slow_path(se == NULL)) {
116+
return NJS_ERROR;
117+
}
118+
119+
se->native = frame->native;
120+
121+
if (se->native) {
122+
se->u.function = frame->function;
123+
124+
} else {
125+
se->u.pc = frame->pc;
126+
}
117127
}
118128

119129
frame = frame->previous;
@@ -123,25 +133,16 @@ njs_error_stack_new(njs_vm_t *vm, njs_object_t *error, njs_value_t *retval)
123133
}
124134
}
125135

126-
njs_string_get(retval, &string);
127-
128-
ret = njs_backtrace_to_string(vm, stack, &string);
129-
130-
njs_arr_destroy(stack);
131-
132-
if (njs_slow_path(ret != NJS_OK)) {
133-
return ret;
134-
}
136+
njs_data(&error->value) = stack;
135137

136-
return njs_string_create(vm, retval, string.start, string.length);
138+
return NJS_OK;
137139
}
138140

139141

140142
njs_int_t
141143
njs_error_stack_attach(njs_vm_t *vm, njs_value_t value)
142144
{
143-
njs_int_t ret;
144-
njs_value_t stack;
145+
njs_int_t ret;
145146

146147
if (njs_slow_path(!njs_is_error(&value))
147148
|| njs_object(&value)->stack_attached)
@@ -153,18 +154,15 @@ njs_error_stack_attach(njs_vm_t *vm, njs_value_t value)
153154
return NJS_OK;
154155
}
155156

156-
ret = njs_error_stack_new(vm, njs_object(&value), &stack);
157+
ret = njs_error_stack_new(vm, value.data.u.object_value);
157158
if (njs_slow_path(ret != NJS_OK)) {
158159
njs_internal_error(vm, "njs_error_stack_new() failed");
159160
return NJS_ERROR;
160161
}
161162

162163
njs_object(&value)->stack_attached = 1;
163164

164-
return njs_object_prop_define(vm, &value,
165-
njs_value_arg(&njs_error_stack_string),
166-
&stack, NJS_OBJECT_PROP_VALUE_CW,
167-
NJS_STACK_HASH);
165+
return NJS_OK;
168166
}
169167

170168

@@ -194,16 +192,20 @@ njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name,
194192
njs_int_t ret;
195193
njs_object_t *error;
196194
njs_object_prop_t *prop;
195+
njs_object_value_t *ov;
197196
njs_lvlhsh_query_t lhq;
198197

199-
error = njs_mp_alloc(vm->mem_pool, sizeof(njs_object_t));
200-
if (njs_slow_path(error == NULL)) {
198+
ov = njs_mp_alloc(vm->mem_pool, sizeof(njs_object_value_t));
199+
if (njs_slow_path(ov == NULL)) {
201200
goto memory_error;
202201
}
203202

203+
njs_set_data(&ov->value, NULL, NJS_DATA_TAG_ANY);
204+
205+
error = &ov->object;
204206
njs_lvlhsh_init(&error->hash);
205207
njs_lvlhsh_init(&error->shared_hash);
206-
error->type = NJS_OBJECT;
208+
error->type = NJS_OBJECT_VALUE;
207209
error->shared = 0;
208210
error->extensible = 1;
209211
error->fast_array = 0;
@@ -485,15 +487,18 @@ const njs_object_init_t njs_aggregate_error_constructor_init = {
485487
void
486488
njs_memory_error_set(njs_vm_t *vm, njs_value_t *value)
487489
{
488-
njs_object_t *object;
490+
njs_object_t *object;
491+
njs_object_value_t *ov;
489492

490-
object = &vm->memory_error_object;
493+
ov = &vm->memory_error_object;
494+
njs_set_data(&ov->value, NULL, NJS_DATA_TAG_ANY);
491495

496+
object = &ov->object;
492497
njs_lvlhsh_init(&object->hash);
493498
njs_lvlhsh_init(&object->shared_hash);
494499
object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_INTERNAL_ERROR);
495500
object->slots = NULL;
496-
object->type = NJS_OBJECT;
501+
object->type = NJS_OBJECT_VALUE;
497502
object->shared = 1;
498503

499504
/*
@@ -691,6 +696,92 @@ njs_error_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
691696
}
692697

693698

699+
static njs_int_t
700+
njs_error_prototype_stack(njs_vm_t *vm, njs_object_prop_t *prop,
701+
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
702+
{
703+
njs_int_t ret;
704+
njs_str_t string;
705+
njs_arr_t *stack, *backtrace;
706+
njs_uint_t i;
707+
njs_value_t rv, *stackval;
708+
njs_stack_entry_t *se;
709+
710+
if (retval != NULL) {
711+
if (!njs_is_error(value)) {
712+
njs_set_undefined(retval);
713+
return NJS_DECLINED;
714+
}
715+
716+
stackval = njs_object_value(value);
717+
718+
if (setval != NULL) {
719+
njs_value_assign(stackval, setval);
720+
return NJS_OK;
721+
}
722+
723+
if (!njs_is_data(stackval, NJS_DATA_TAG_ANY)) {
724+
njs_value_assign(retval, stackval);
725+
return NJS_OK;
726+
}
727+
728+
stack = njs_data(stackval);
729+
if (stack == NULL) {
730+
njs_set_undefined(retval);
731+
return NJS_OK;
732+
}
733+
734+
se = stack->start;
735+
736+
backtrace = njs_arr_create(vm->mem_pool, stack->items,
737+
sizeof(njs_backtrace_entry_t));
738+
if (njs_slow_path(backtrace == NULL)) {
739+
return NJS_ERROR;
740+
}
741+
742+
for (i = 0; i < stack->items; i++) {
743+
if (njs_add_backtrace_entry(vm, backtrace, &se[i]) != NJS_OK) {
744+
return NJS_ERROR;
745+
}
746+
}
747+
748+
ret = njs_error_to_string2(vm, &rv, value, 0);
749+
if (njs_slow_path(ret != NJS_OK)) {
750+
return ret;
751+
}
752+
753+
njs_string_get(&rv, &string);
754+
755+
ret = njs_backtrace_to_string(vm, backtrace, &string);
756+
757+
njs_arr_destroy(backtrace);
758+
njs_arr_destroy(stack);
759+
760+
if (njs_slow_path(ret != NJS_OK)) {
761+
return ret;
762+
}
763+
764+
ret = njs_string_create(vm, stackval, string.start, string.length);
765+
if (njs_slow_path(ret != NJS_OK)) {
766+
return ret;
767+
}
768+
769+
njs_value_assign(retval, stackval);
770+
771+
return NJS_OK;
772+
}
773+
774+
/* Delete. */
775+
776+
if (njs_is_error(value)) {
777+
stackval = njs_object_value(value);
778+
njs_set_data(stackval, NULL, NJS_DATA_TAG_ANY);
779+
}
780+
781+
return NJS_OK;
782+
}
783+
784+
694785
njs_int_t
695786
njs_error_to_string(njs_vm_t *vm, njs_value_t *retval, const njs_value_t *error)
696787
{
@@ -717,6 +808,9 @@ static const njs_object_prop_t njs_error_prototype_properties[] =
717808
NJS_DECLARE_PROP_NATIVE("valueOf", njs_error_prototype_value_of, 0, 0),
718809

719810
NJS_DECLARE_PROP_NATIVE("toString", njs_error_prototype_to_string, 0, 0),
811+
812+
NJS_DECLARE_PROP_HANDLER("stack", njs_error_prototype_stack,
813+
0, 0, NJS_OBJECT_PROP_VALUE_CW),
720814
};
721815

722816

@@ -986,14 +1080,14 @@ const njs_object_type_init_t njs_aggregate_error_type_init = {
9861080

9871081
static njs_int_t
9881082
njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
989-
njs_native_frame_t *native_frame)
1083+
njs_stack_entry_t *se)
9901084
{
9911085
njs_int_t ret;
9921086
njs_vm_code_t *code;
9931087
njs_function_t *function;
9941088
njs_backtrace_entry_t *be;
9951089

996-
function = native_frame->function;
1090+
function = se->native ? se->u.function : NULL;
9971091

9981092
if (function != NULL && function->bound != NULL) {
9991093
/* Skip. */
@@ -1019,7 +1113,7 @@ njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
10191113
return NJS_OK;
10201114
}
10211115

1022-
code = njs_lookup_code(vm, native_frame->pc);
1116+
code = njs_lookup_code(vm, se->u.pc);
10231117

10241118
if (code != NULL) {
10251119
be->name = code->name;
@@ -1028,7 +1122,7 @@ njs_add_backtrace_entry(njs_vm_t *vm, njs_arr_t *stack,
10281122
be->name = njs_entry_anonymous;
10291123
}
10301124

1031-
be->line = njs_lookup_line(code->lines, native_frame->pc - code->start);
1125+
be->line = njs_lookup_line(code->lines, se->u.pc - code->start);
10321126
if (!vm->options.quiet) {
10331127
be->file = code->file;
10341128
}

src/njs_vm.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ struct njs_vm_s {
165165
* MemoryError is statically allocated immutable Error object
166166
* with the InternalError prototype.
167167
*/
168-
njs_object_t memory_error_object;
168+
njs_object_value_t memory_error_object;
169169

170170
njs_object_t string_object;
171171
njs_object_t global_object;

src/test/njs_benchmark.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ njs_benchmark_test(njs_vm_t *parent, njs_opts_t *opts, njs_value_t *report,
116116

117117
njs_vm_opt_init(&options);
118118

119+
options.backtrace = 1;
119120
options.addons = njs_benchmark_addon_external_modules;
120121

121122
vm = NULL;
@@ -468,6 +469,16 @@ static njs_benchmark_test_t njs_test[] =
468469
njs_str("$shared.method('YES')"),
469470
njs_str("shared"),
470471
1000 },
472+
473+
{ "exception",
474+
njs_str("function f() { try { throw new Error('test') } catch (e) { return e.message } } [f].map(v=>v())[0]"),
475+
njs_str("test"),
476+
10000 },
477+
478+
{ "exception.stack",
479+
njs_str("function f() { try { throw new Error('test') } catch (e) { return e.stack } } [f].map(v=>v())[0]"),
480+
njs_str("Error: test\n at f (:1)\n at anonymous (:1)\n at Array.prototype.map (native)\n at main (:1)\n"),
481+
100 },
471482
};
472483

473484

0 commit comments

Comments
 (0)