From e4a8d2617b49727c4de09ad9ca9bc8fa270adf1e Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sat, 21 Apr 2012 07:13:25 +0200 Subject: [PATCH] addon: add AtExit() function Lets native addons register exit hooks that run after the event loop has quit but before the VM is killed. Fixes #3147. --- src/node.cc | 32 ++++++++++++++++++++++++ src/node.h | 5 ++++ test/addons/at-exit/binding.cc | 43 +++++++++++++++++++++++++++++++++ test/addons/at-exit/binding.gyp | 8 ++++++ test/addons/at-exit/test.js | 1 + 5 files changed, 89 insertions(+) create mode 100644 test/addons/at-exit/binding.cc create mode 100644 test/addons/at-exit/binding.gyp create mode 100644 test/addons/at-exit/test.js diff --git a/src/node.cc b/src/node.cc index 2cbd5c5cb940c0..e8ce6d6f83c79b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2770,6 +2770,37 @@ char** Init(int argc, char *argv[]) { } +struct AtExitCallback { + AtExitCallback* next_; + void (*cb_)(void* arg); + void* arg_; +}; + +static AtExitCallback* at_exit_functions_; + + +void RunAtExit() { + AtExitCallback* p = at_exit_functions_; + at_exit_functions_ = NULL; + + while (p) { + AtExitCallback* q = p->next_; + p->cb_(p->arg_); + delete p; + p = q; + } +} + + +void AtExit(void (*cb)(void* arg), void* arg) { + AtExitCallback* p = new AtExitCallback; + p->cb_ = cb; + p->arg_ = arg; + p->next_ = at_exit_functions_; + at_exit_functions_ = p; +} + + void EmitExit(v8::Handle process_l) { // process.emit('exit') process_l->Set(String::NewSymbol("_exiting"), True()); @@ -2850,6 +2881,7 @@ int Start(int argc, char *argv[]) { uv_run(uv_default_loop()); EmitExit(process_l); + RunAtExit(); #ifndef NDEBUG // Clean up. diff --git a/src/node.h b/src/node.h index a2cf2eb9bbfcc2..aed1078b21bda2 100644 --- a/src/node.h +++ b/src/node.h @@ -250,6 +250,11 @@ node_module_struct* get_builtin_module(const char *name); #define NODE_MODULE_DECL(modname) \ extern "C" node::node_module_struct modname ## _module; +/* Called after the event loop exits but before the VM is disposed. + * Callbacks are run in reverse order of registration, i.e. newest first. + */ +NODE_EXTERN void AtExit(void (*cb)(void* arg), void* arg = 0); + NODE_EXTERN void SetErrno(uv_err_t err); NODE_EXTERN v8::Handle MakeCallback(const v8::Handle object, diff --git a/test/addons/at-exit/binding.cc b/test/addons/at-exit/binding.cc new file mode 100644 index 00000000000000..6558a08bf9e167 --- /dev/null +++ b/test/addons/at-exit/binding.cc @@ -0,0 +1,43 @@ +#undef NDEBUG +#include +#include +#include +#include + +using node::AtExit; +using v8::Handle; +using v8::HandleScope; +using v8::Local; +using v8::Object; + +static char cookie[] = "yum yum"; +static int at_exit_cb1_called = 0; +static int at_exit_cb2_called = 0; + +static void at_exit_cb1(void* arg) { + HandleScope scope; + assert(arg == 0); + Local obj = Object::New(); + assert(!obj.IsEmpty()); // assert VM is still alive + assert(obj->IsObject()); + at_exit_cb1_called++; +} + +static void at_exit_cb2(void* arg) { + assert(arg == static_cast(cookie)); + at_exit_cb2_called++; +} + +static void sanity_check(void) { + assert(at_exit_cb1_called == 1); + assert(at_exit_cb2_called == 2); +} + +void init(Handle target) { + AtExit(at_exit_cb1); + AtExit(at_exit_cb2, cookie); + AtExit(at_exit_cb2, cookie); + atexit(sanity_check); +} + +NODE_MODULE(binding, init); diff --git a/test/addons/at-exit/binding.gyp b/test/addons/at-exit/binding.gyp new file mode 100644 index 00000000000000..3bfb84493f3e87 --- /dev/null +++ b/test/addons/at-exit/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/at-exit/test.js b/test/addons/at-exit/test.js new file mode 100644 index 00000000000000..4466b898afe597 --- /dev/null +++ b/test/addons/at-exit/test.js @@ -0,0 +1 @@ +var binding = require('./build/Release/binding');