diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index a04b9602ec..8ca2c6225d 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -128,17 +128,53 @@ typedef uint32_t jerry_value_t; Structure that defines how a context data item will be initialized and deinitialized. JerryScript zeroes out the memory for the item by default, and if the `init_cb` field is not NULL, it will be called with the pointer to the memory as -an additional custom initializer. The `deinit_cb` (if non-`NULL`) is called during a call to `jerry_cleanup()` to run -any custom deinitialization. +an additional custom initializer. The `deinit_cb` (if non-`NULL`) is called during a call to `jerry_cleanup ()` to run +any custom deinitialization *before* the VM has been fully cleaned up. The `finalize_cb` (if non-`NULL`) is also called +during a call to `jerry_cleanup ()` to run any custom deinitialization *after* the VM has been fully cleaned up. **Prototype** ```c typedef struct { - void (*init_cb) (void *); /**< callback responsible for initializing a context item, or NULL */ - void (*deinit_cb) (void *); /**< callback responsible for deinitializing a context item, or NULL */ - size_t bytes_needed; /**< number of bytes to allocate for this manager */ + /** + * Callback responsible for initializing a context item, or NULL to zero out the memory. This is called lazily, the + * first time jerry_get_context_data () is called with this manager. + * + * @param [in] data The buffer that JerryScript allocated for the manager. The buffer is zeroed out. The size is + * determined by the bytes_needed field. The buffer is kept alive until jerry_cleanup () is called. + */ + void (*init_cb) (void *data); + + /** + * Callback responsible for deinitializing a context item, or NULL. This is called as part of jerry_cleanup (), + * right *before* the VM has been cleaned up. This is a good place to release strong references to jerry_value_t's + * that the manager may be holding. + * Note: because the VM has not been fully cleaned up yet, jerry_object_native_info_t free_cb's can still get called + * *after* all deinit_cb's have been run. See finalize_cb for a callback that is guaranteed to run *after* all + * free_cb's have been run. + * + * @param [in] data The buffer that JerryScript allocated for the manager. + */ + void (*deinit_cb) (void *data); + + /** + * Callback responsible for finalizing a context item, or NULL. This is called as part of jerry_cleanup (), + * right *after* the VM has been cleaned up and destroyed and jerry_... APIs cannot be called any more. At this point, + * all values in the VM have been cleaned up. This is a good place to clean up native state that can only be cleaned + * up at the very end when there are no more VM values around that may need to access that state. + * + * @param [in] data The buffer that JerryScript allocated for the manager. After returning from this callback, + * the data pointer may no longer be used. + */ + void (*finalize_cb) (void *data); + + /** + * Number of bytes to allocate for this manager. This is the size of the buffer that JerryScript will allocate on + * behalf of the manager. The pointer to this buffer is passed into init_cb, deinit_cb and finalize_cb. It is also + * returned from the jerry_get_context_data () API. + */ + size_t bytes_needed; } jerry_context_data_manager_t; ``` diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index dfafe6b8fe..989f815c21 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -210,24 +210,35 @@ jerry_cleanup (void) } #endif /* JERRY_DEBUGGER */ - for (jerry_context_data_header_t *this_p = JERRY_CONTEXT (context_data_p), *next_p = NULL; + for (jerry_context_data_header_t *this_p = JERRY_CONTEXT (context_data_p); this_p != NULL; - this_p = next_p) + this_p = this_p->next_p) { - next_p = this_p->next_p; if (this_p->manager_p->deinit_cb) { this_p->manager_p->deinit_cb (JERRY_CONTEXT_DATA_HEADER_USER_DATA (this_p)); } - jmem_heap_free_block (this_p, sizeof (jerry_context_data_header_t) + this_p->manager_p->bytes_needed); } #ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN ecma_free_all_enqueued_jobs (); #endif /* CONFIG_DISABLE_ES2015_PROMISE_BUILTIN */ ecma_finalize (); - jmem_finalize (); jerry_make_api_unavailable (); + + for (jerry_context_data_header_t *this_p = JERRY_CONTEXT (context_data_p), *next_p = NULL; + this_p != NULL; + this_p = next_p) + { + next_p = this_p->next_p; + if (this_p->manager_p->finalize_cb) + { + this_p->manager_p->finalize_cb (JERRY_CONTEXT_DATA_HEADER_USER_DATA (this_p)); + } + jmem_heap_free_block (this_p, sizeof (jerry_context_data_header_t) + this_p->manager_p->bytes_needed); + } + + jmem_finalize (); } /* jerry_cleanup */ /** diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 46ba3308a5..7a0811c179 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -233,9 +233,44 @@ typedef bool (*jerry_objects_foreach_by_native_info_t) (const jerry_value_t obje */ typedef struct { - void (*init_cb) (void *); /**< callback responsible for initializing a context item, or NULL to zero out the memory */ - void (*deinit_cb) (void *); /**< callback responsible for deinitializing a context item, or NULL */ - size_t bytes_needed; /**< number of bytes to allocate for this manager */ + /** + * Callback responsible for initializing a context item, or NULL to zero out the memory. This is called lazily, the + * first time jerry_get_context_data () is called with this manager. + * + * @param [in] data The buffer that JerryScript allocated for the manager. The buffer is zeroed out. The size is + * determined by the bytes_needed field. The buffer is kept alive until jerry_cleanup () is called. + */ + void (*init_cb) (void *data); + + /** + * Callback responsible for deinitializing a context item, or NULL. This is called as part of jerry_cleanup (), + * right *before* the VM has been cleaned up. This is a good place to release strong references to jerry_value_t's + * that the manager may be holding. + * Note: because the VM has not been fully cleaned up yet, jerry_object_native_info_t free_cb's can still get called + * *after* all deinit_cb's have been run. See finalize_cb for a callback that is guaranteed to run *after* all + * free_cb's have been run. + * + * @param [in] data The buffer that JerryScript allocated for the manager. + */ + void (*deinit_cb) (void *data); + + /** + * Callback responsible for finalizing a context item, or NULL. This is called as part of jerry_cleanup (), + * right *after* the VM has been cleaned up and destroyed and jerry_... APIs cannot be called any more. At this point, + * all values in the VM have been cleaned up. This is a good place to clean up native state that can only be cleaned + * up at the very end when there are no more VM values around that may need to access that state. + * + * @param [in] data The buffer that JerryScript allocated for the manager. After returning from this callback, + * the data pointer may no longer be used. + */ + void (*finalize_cb) (void *data); + + /** + * Number of bytes to allocate for this manager. This is the size of the buffer that JerryScript will allocate on + * behalf of the manager. The pointer to this buffer is passed into init_cb, deinit_cb and finalize_cb. It is also + * returned from the jerry_get_context_data () API. + */ + size_t bytes_needed; } jerry_context_data_manager_t; /** diff --git a/tests/unit-core/test-context-data.c b/tests/unit-core/test-context-data.c index 52e3a2ed0a..56cefcefe0 100644 --- a/tests/unit-core/test-context-data.c +++ b/tests/unit-core/test-context-data.c @@ -19,9 +19,10 @@ static bool test_context_data1_new_called = false; static bool test_context_data2_new_called = false; +static bool test_context_data3_new_called = false; static bool test_context_data1_free_called = false; static bool test_context_data2_free_called = false; -static bool test_context_data3_new_called = false; +static bool test_context_data1_finalize_called = false; /* Context item 1 */ const char *string1 = "item1"; @@ -38,12 +39,23 @@ test_context_data1_free (void *user_data_p) { test_context_data1_free_called = true; TEST_ASSERT ((*(const char **) user_data_p) == string1); + TEST_ASSERT (!test_context_data1_finalize_called); } /* test_context_data1_free */ +static void +test_context_data1_finalize (void *user_data_p) +{ + TEST_ASSERT (test_context_data1_free_called); + TEST_ASSERT (!test_context_data1_finalize_called); + TEST_ASSERT ((*(const char **) user_data_p) == string1); + test_context_data1_finalize_called = true; +} /* test_context_data1_finalize */ + static const jerry_context_data_manager_t manager1 = { .init_cb = test_context_data1_new, .deinit_cb = test_context_data1_free, + .finalize_cb = test_context_data1_finalize, .bytes_needed = sizeof (const char *) }; @@ -86,6 +98,7 @@ static const jerry_context_data_manager_t manager3 = .init_cb = test_context_data3_new, /* NULL is allowed: */ .deinit_cb = NULL, + .finalize_cb = NULL, .bytes_needed = 0, };