inits
A simple init system for Node.js.
Manages initialization tasks, and optionally also shutdown tasks.
Useful to simplify initialization of complex systems
with asynchronous tasks.
Installation
Simply run:
npm install inits
Or add inits
to your package.json
:
"dependencies": {
...
"inits": "*",
...
},
and run npm install
.
Rationale
The typical use case is this:
you have several asynchronous initialization tasks and you need to them to proceed orderly.
Sometimes your web server is starting up and accepting requests before your database connection is up,
so you are showing an error some of the time.
Your notification system is getting tripped by these errors.
So you add a delay to the database initialization but as your system grows in complexity
there are more and more initialization tasks and again errors creep up once in a while.
Same thing happens during shutdown:
you have a queue that accumulates data and writes it to the database every minute,
and you want to clear the queue before closing down.
But as a good citizen you also want to close your database connections.
Unless you are careful, the database may be closed when you try to write that data out.
inits
adds an init
phase where you can stick all of these asynchronous initialization tasks,
and a start
phase to start up servers.
Symmetrically there is a stop
phase during which servers and queues are closed,
and then a finish
phase for final shutdown tasks.
Example:
inits.init(function(callback)
{
db.initialize(function(error)
{
if (error) return callback(error);
db.createPool(callback);
});
});
inits.finish(function(callback)
{
db.flush(function(error)
{
if (error) return callback(error);
db.destroyPool(callback);
});
});
An additional advantage of using inits
is that all initialization code runs in a domain,
thus catching errors and uncaught exceptions.
API
The following functions and events are exported directly by inits
.
inits.init(task)
Add an asynchronous task to the init phase.
The task will receive a callback parameter of the form
function(error)
; see below.
Example:
inits.init(function(callback)
{
doSomething(function(error)
{
if (error)
{
console.error('failure: %s', error);
callback(error);
}
console.log('success');
callback(null);
});
});
inits.start(task)
Add an asynchronous task to be invoked when starting
(start phase, after init).
inits.stop(task)
Add an asynchronous task to be invoked when stopping
(stop phase).
inits.finish(task)
Add an asynchronous task to be invoked before finishing
(finish phase, after stop phase).
inits.standalone(task)
Set an asynchronous task as standalone.
Useful when your script consists solely of a task
that must run after startup, followed by shutdown.
Event: 'ready'
Sent when initialization has finished and the system is ready.
Event: 'shutdown'
Sent when the system is starting an ordered shutdown:
it will run the tasks for stop
and finish
, then exit.
Event: 'end'
Sent after the system has finished and is about to exit.
Can be used e.g. to print a warning message.
Event: 'error'
Sent when there is an error in any phase.
Events: 'initing', 'starting', 'stopping', 'finishing'
Sent before the corresponding phases have run.
Events: 'inited', 'started', 'stopped', 'finished'
Sent after the corresponding phases have run.
Tasks
All asynchronous tasks passed to the four phases and to standalone()
must accept as parameter a callback of the form function(error)
, following the Node.js convention;
and chain-call them at the end with either an error
or a falsy value (null
, undefined
, nothing) to signal success.
Example:
inits.init(function(callback)
{
DatabaseDriver.connect(url, function(error, connected)
{
if (error)
{
return callback(error);
}
db = connected;
callback(null);
});
});
Note how the callback
is invoked before the function ends;
this allows inits
to run asynchronous tasks,
and to regain execution and run any other tasks.
If your task is synchronous, simply invoke the callback at the end:
inits.finish(function(callback)
{
db.close();
callback(null);
});
Task Order
Tasks can be ordered to run in a certain sequence.
When adding a task you can specify its order as first parameter:
inits.init(1, function(callback)
{
db.close();
callback(null);
});
inits
will run first all tasks with order 1
,
then all tasks with order 2
,
and so on.
Finally it will run all tasks without specific order.
Options
To configure inits
you can set some attributes in inits.options
that will modify how the init system behaves.
catchErrors
If set to true
(or any other truthy value),
inits
will catch uncaught exceptions and errors
and shutdown automatically when any of those happens.
Default: true
.
Example:
inits.options.catchErrors = false;
catchSignals
If set to true
(or any other truthy value),
inits
will intercept SIGTERM and SIGKILL (e.g. control-C) signals
and shutdown when one of them is received.
Default: true
.
exitProcess
If set to true
(or any other truthy value),
inits
will exit after shutdown
(with code 0 if successful, or code 1 if it fails).
Default: true
.
showErrors
If set to true
(or any other truthy value),
inits
will show a log message for every error.
Default: true
.
showTraces
If set to true
(or any other truthy value),
inits
will show the trace for error messages.
Default: false
.
logTimes
If set to true
(or any other truthy value),
inits
will log how long initialization and shutdown took.
Default: true
.
stopOnError
If set to true
(or any other truthy value),
if a task in any phase returns an error the phase will stop.
If false
errors will just be logged (if configured).
Default: false
.
maxTaskTimeSec
If any task takes more than this number of seconds,
a warning will be shown on the log.
Default: 10.
initInParallel, startInParallel, stopInParallel, finishInParallel
If any of these is set to true
(or any other truthy value),
then tasks in the corresponding phase
(init, start, stop or finish)
are run in parallel.
Each task is invoked before waiting for the last one to end.
The phase will only end once all of the tasks have ended.
Default: false
, which means tasks run in sequence.
Lifecycle of a System
There are four distinct phases in inits
:
- init,
- start,
- stop,
- and finish.
They are intended to be symmetric:
if a certain capability is open in init
it should be closed in finish
,
and whatever starts in start
should be stopped in (surprise!) stop
.
Init Phase
Initialization tasks, ideal for low-level stuff
such as connecting to the database.
The init system will make sure that all require
'd code files have been loaded
before starting this phase.
Start Phase
Tasks to start the system, such as starting a web server.
These run after all init
tasks have finished.
Stop Phase
Tasks to stop the system, such as stopping any open servers.
These run when the system initiates shutdown: either by a signal
(SIGTERM, SIGKILL or control-C) or by an uncaught exception
or an error.
Finish Phase
Final shutdown tasks, such as disconnecting from the database:
whatever needs to be done before the system definitely closes down.
Guarantees
inits
makes the following guarantees:
- All tasks in the
init
phase are run before the start
phase. - All tasks in the
start
phase are run before the ready
event. - Only one standalone task is called, after the
start
phase
and before the stop
phase. - All tasks in the
stop
phase are run after an error or a SIGTERM or SIGKILL signal. - All tasks in the
finish
phase are run before finishing,
except if there is an error while starting up or shutting down.
(Note: before version 0.1.15 finish tasks were run if there were
errors during any phase.) - The
end
event is only sent if shutdown finishes successfully,
which includes both the stop
and finish
phases. - When no order is specified,
all tasks in any phase are chained serially and in the order they were added:
each task runs when the previous one has finished
(but only if it invoked the parameter callback without an error).
- When order is specified, tasks with a given order will run before all other tasks with larger order,
and after all other tasks with smaller order.
- Tasks with specified order will run before tasks without specified order.
These guarantees only apply if tasks do not finish in error.
In that case the process will try to shutdown,
but if the error happens while starting up or shutting down
inits
will just exit.
If you notice any deviation from these behaviors,
please report an issue.
Other Lifecycles
Sometimes four phases are not enough.
inits
might be designed to support custom phases in the future
if there is interest; just create an issue if you are interested,
or even better, send a pull request.
Unexpected Exit
In Node.js v0.12.x and io.js, there is an event
beforeExit
that can be used to force an ordered shutdown when there is nothing else to do
and the event loop empties.
However in Node.js v0.10.x there is no official way to catch this situation;
the process can just finish without running the stop
or finish
tasks.
rather than rely on complex intervals we have opted to just
let Node.js finish, but alert the user about this.
If you don't want your process to exit unexpectedly you can use a standalone
task.
You may also create a setInterval()
,
or just keep your servers running and not call unref()
on them.
In this last case your processes will keep running and only exit
when the appropriate signal arrives.
All of these methods will prevent your process from finishing without
running the stop
and finish
tasks.
Full example
How to make a web server that connects to a MongoDB database.
We will hook database startup to the init
phase,
and server start to the start
phase.
On stop
we stop the server,
and on finish
we close the connection to the database.
var inits = require('inits');
var MongoClient = require('mongodb').MongoClient
var mongodb = require('mongodb');
var http = require('http');
var db;
inits.init(function(callback)
{
MongoClient.connect(url, function(error, connected)
{
if (error)
{
return callback(error);
}
db = connected;
callback(null);
});
});
inits.start(function(callback)
{
server = http.createServer(listener);
server.on('error', function(error)
{
return callback(error);
});
server.on('listening', function()
{
callback(null);
});
});
inits.stop(function(callback)
{
server.close(callback);
});
inits.finish(function(callback)
{
db.close();
callback(null);
});
Licensed under The MIT License
Copyright (c) 2015 Alex Fernández alexfernandeznpm@gmail.com
and 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.