diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index a3ebaa40b54..c25a5c84f51 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -267,6 +267,12 @@ UV_EXTERN uv_loop_t* uv_default_loop(void); */ UV_EXTERN int uv_run(uv_loop_t*, uv_run_mode mode); +/* + * This function checks whether the reference count, the number of active + * handles or requests left in the event loop, is non-zero. + */ +UV_EXTERN int uv_loop_alive(uv_loop_t* loop); + /* * This function will stop the event loop by forcing uv_run to end * as soon as possible, but not sooner than the next loop iteration. diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c index 98b48989a74..a49285514ee 100644 --- a/deps/uv/src/unix/core.c +++ b/deps/uv/src/unix/core.c @@ -291,6 +291,11 @@ static int uv__loop_alive(uv_loop_t* loop) { } +int uv_loop_alive(uv_loop_t* loop) { + return uv__loop_alive(loop); +} + + int uv_run(uv_loop_t* loop, uv_run_mode mode) { int timeout; int r; diff --git a/deps/uv/src/win/core.c b/deps/uv/src/win/core.c index 3ba3203803f..f6c17af9d98 100644 --- a/deps/uv/src/win/core.c +++ b/deps/uv/src/win/core.c @@ -265,6 +265,11 @@ static int uv__loop_alive(uv_loop_t* loop) { } +int uv_loop_alive(uv_loop_t* loop) { + return uv__loop_alive(loop); +} + + int uv_run(uv_loop_t *loop, uv_run_mode mode) { int r; void (*poll)(uv_loop_t* loop, int block); diff --git a/doc/api/process.markdown b/doc/api/process.markdown index 7add27e96c2..81e48971bc5 100644 --- a/doc/api/process.markdown +++ b/doc/api/process.markdown @@ -22,6 +22,18 @@ Example of listening for `exit`: console.log('About to exit.'); }); + +## Event: 'beforeExit' + +This event is emitted when node empties its event loop and has nothing else to +schedule. Normally, node exits when there is no work scheduled, but a listener +for 'beforeExit' can make asynchronous calls, and cause node to continue. + +'beforeExit' is not emitted for conditions causing explicit termination, such as +`process.exit()` or uncaught exceptions, and should not be used as an +alternative to the 'exit' event unless the intention is to schedule more work. + + ## Event: 'uncaughtException' Emitted when an exception bubbles all the way back to the event loop. If a diff --git a/src/node.cc b/src/node.cc index b681c292afc..5eaf43d6a49 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3033,6 +3033,14 @@ void EmitExit(v8::Handle<v8::Object> process_l) { MakeCallback(process, "emit", ARRAY_SIZE(args), args); } + +void EmitBeforeExit(Handle<Object> process_l) { + // process.emit('beforeExit') + Local<Value> args[] = { String::New("beforeExit"), Integer::New(0, node_isolate) }; + MakeCallback(process, "emit", ARRAY_SIZE(args), args); +} + + static char **copy_argv(int argc, char **argv) { size_t strlen_sum; char **argv_copy; @@ -3093,12 +3101,18 @@ int Start(int argc, char *argv[]) { // so your next reading stop should be node::Load()! Load(process_l); - // All our arguments are loaded. We've evaluated all of the scripts. We - // might even have created TCP servers. Now we enter the main eventloop. If - // there are no watchers on the loop (except for the ones that were - // uv_unref'd) then this function exits. As long as there are active - // watchers, it blocks. - uv_run(uv_default_loop(), UV_RUN_DEFAULT); + do { + // All our arguments are loaded. We've evaluated all of the scripts. We + // might even have created TCP servers. Now we enter the main eventloop. If + // there are no watchers on the loop (except for the ones that were + // uv_unref'd) then this function exits. As long as there are active + // watchers, it blocks. + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + EmitBeforeExit(process_l); + + // 'keepalive' event may have brought the loop back to life + } while(uv_loop_alive(uv_default_loop())); EmitExit(process_l); RunAtExit(); diff --git a/test/simple/test-beforeexit-event-exit.js b/test/simple/test-beforeexit-event-exit.js new file mode 100644 index 00000000000..be6f2c607c3 --- /dev/null +++ b/test/simple/test-beforeexit-event-exit.js @@ -0,0 +1,28 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert'); + +process.on('beforeExit', function() { + assert(false, 'exit should not allow this to occur'); +}); + +process.exit(); diff --git a/test/simple/test-beforeexit-event.js b/test/simple/test-beforeexit-event.js new file mode 100644 index 00000000000..3d011fdeacb --- /dev/null +++ b/test/simple/test-beforeexit-event.js @@ -0,0 +1,58 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert'); +var net = require('net'); +var util = require('util'); +var revivals = 0; +var deaths = 0; + +process.on('beforeExit', function() { deaths++; } ); + +process.once('beforeExit', tryImmediate); + +function tryImmediate() { + setImmediate(function() { + revivals++; + process.once('beforeExit', tryTimer); + }); +} + +function tryTimer() { + setTimeout(function () { + revivals++; + process.once('beforeExit', tryListen); + }, 1); +} + +function tryListen() { + net.createServer() + .listen(0) + .on('listening', function() { + revivals++; + this.close(); + }); +} + +process.on('exit', function() { + assert(4 === deaths); + assert(3 === revivals); +});