Introduced njs.on('exit') callback support.
diff --git a/src/njs_builtin.c b/src/njs_builtin.c
index 1d6ff5d..6a06052 100644
--- a/src/njs_builtin.c
+++ b/src/njs_builtin.c
@@ -851,7 +851,7 @@
 
 
 static njs_int_t
-njs_dump_value(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+njs_ext_dump(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
     uint32_t     n;
@@ -878,6 +878,62 @@
 
 
 static njs_int_t
+njs_ext_on(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    njs_str_t    type;
+    njs_uint_t   i, n;
+    njs_value_t  *value;
+
+    static const struct {
+        njs_str_t   name;
+        njs_uint_t  id;
+    } hooks[] = {
+        {
+            njs_str("exit"),
+            NJS_HOOK_EXIT
+        },
+    };
+
+    value = njs_arg(args, nargs, 1);
+
+    if (njs_slow_path(!njs_is_string(value))) {
+        njs_type_error(vm, "hook type is not a string");
+        return NJS_ERROR;
+    }
+
+    njs_string_get(value, &type);
+
+    i = 0;
+    n = sizeof(hooks) / sizeof(hooks[0]);
+
+    while (i < n) {
+        if (njs_strstr_eq(&type, &hooks[i].name)) {
+            break;
+        }
+
+        i++;
+    }
+
+    if (i == n) {
+        njs_type_error(vm, "unknown hook type \"%V\"", &type);
+        return NJS_ERROR;
+    }
+
+    value = njs_arg(args, nargs, 2);
+
+    if (njs_slow_path(!njs_is_function(value) && !njs_is_null(value))) {
+        njs_type_error(vm, "callback is not a function or null");
+        return NJS_ERROR;
+    }
+
+    vm->hooks[i] = njs_is_function(value) ? njs_function(value) : NULL;
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
 njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
     njs_value_t *global, njs_value_t *setval, njs_value_t *retval)
 {
@@ -1629,7 +1685,14 @@
     {
         .type = NJS_PROPERTY,
         .name = njs_string("dump"),
-        .value = njs_native_function(njs_dump_value, 0),
+        .value = njs_native_function(njs_ext_dump, 0),
+        .configurable = 1,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_string("on"),
+        .value = njs_native_function(njs_ext_on, 0),
         .configurable = 1,
     },
 };
diff --git a/src/njs_vm.c b/src/njs_vm.c
index 366a5e5..22e4ee7 100644
--- a/src/njs_vm.c
+++ b/src/njs_vm.c
@@ -91,6 +91,10 @@
     njs_event_t        *event;
     njs_lvlhsh_each_t  lhe;
 
+    if (vm->hooks[NJS_HOOK_EXIT] != NULL) {
+        (void) njs_vm_call(vm, vm->hooks[NJS_HOOK_EXIT], NULL, 0);
+    }
+
     if (njs_waiting_events(vm)) {
         njs_lvlhsh_each_init(&lhe, &njs_event_hash_proto);
 
diff --git a/src/njs_vm.h b/src/njs_vm.h
index 47f0f87..d6ac1b8 100644
--- a/src/njs_vm.h
+++ b/src/njs_vm.h
@@ -175,6 +175,12 @@
       + njs_scope_offset(index)))
 
 
+enum njs_hook_e {
+    NJS_HOOK_EXIT = 0,
+    NJS_HOOK_MAX
+};
+
+
 struct njs_vm_s {
     /* njs_vm_t must be aligned to njs_value_t due to scratch value. */
     njs_value_t              retval;
@@ -210,6 +216,8 @@
     njs_object_prototype_t   prototypes[NJS_OBJ_TYPE_MAX];
     njs_function_t           constructors[NJS_OBJ_TYPE_MAX];
 
+    njs_function_t           *hooks[NJS_HOOK_MAX];
+
     njs_mp_t                 *mem_pool;
 
     u_char                   *start;
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index ccc1de1..e49cc08 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -17197,6 +17197,23 @@
               "decodeURI.name = 'XXX'; njs.dump(decodeURI)"),
       njs_str("[Function: XXX]") },
 
+    /* njs.on(). */
+
+    { njs_str("njs.on(decodeURI)"),
+      njs_str("TypeError: hook type is not a string") },
+
+    { njs_str("njs.on('xxx')"),
+      njs_str("TypeError: unknown hook type \"xxx\"") },
+
+    { njs_str("njs.on('exit')"),
+      njs_str("TypeError: callback is not a function or null") },
+
+    { njs_str("njs.on('exit', null); 1"),
+      njs_str("1") },
+
+    { njs_str("njs.on('exit', ()=>{}); 1"),
+      njs_str("1") },
+
     /* Built-in methods name. */
 
     { njs_str(