pooled-pg
Version:
A driver to PostgreSQL that is compatible with pg, with more effective pooling strategies.
271 lines (243 loc) • 4.99 kB
JavaScript
;
/**
* A server that listens to requests and serves them using pg.
* (C) 2015 Alex Fernández.
*/
// requires
require('prototypes');
var net = require('net');
var Log = require('log');
var cluster = require('cluster');
var testing = require('testing');
var profiler = require('microprofiler');
var numCPUs = require('os').cpus().length;
var EventEmitter = require('events').EventEmitter;
var pooled = require('./pooled.js');
var protocol = require('./protocol.js');
// globals
var log = new Log('info');
/**
* Start a pg server.
*/
exports.start = function(options, callback)
{
if (!options.cluster)
{
return startServer(options, callback);
}
if (cluster.isMaster)
{
for (var i = 0; i < numCPUs; i++)
{
cluster.fork();
}
cluster.on('exit', function(worker, code, signal)
{
log.error('Worker died with code %s, signal %s', worker.process.pid, code, signal);
cluster.fork();
});
return callback(null, 'cluster');
}
else if (cluster.isWorker)
{
return startServer(options, callback);
}
};
function startServer(options, callback)
{
if (options.silent)
{
log = new Log('notice');
pooled.setLogLevel('notice');
}
else if (options.debug)
{
log = new Log('debug');
pooled.setLogLevel('debug');
}
var server = new Server(options);
server.start(callback);
return server;
}
exports.end = function()
{
pooled.end();
};
var Server = function(options)
{
// self-reference
var self = this;
// attributes
var server;
var connections = [];
self.start = function(callback)
{
server = net.createServer(function(socket)
{
var connection = new Connection(options);
connections.push(connection);
connection.init(socket);
});
server.listen(options.port, function(error)
{
if (error)
{
return callback(error);
}
return callback(null, self);
});
};
self.close = function(callback)
{
connections.forEach(function(connection)
{
connection.close();
});
server.close(callback);
};
self.toString = function()
{
return 'pooled';
};
};
function testStart(callback)
{
var options = {
port: 5445,
test: true,
};
exports.start(options, function(error, server)
{
testing.check(error, 'Could not start server', callback);
server.close(function(error)
{
testing.check(error, 'Could not close server', callback);
testing.success(callback);
});
});
}
var Connection = function(options)
{
// self-reference
var self = this;
// attributes
self.socket = null;
var parser = new protocol.Parser();
self.init = function(socket)
{
log.debug('Initing socket');
socket.setNoDelay();
socket.on('data', receive);
socket.on('error', handle);
self.socket = socket;
parser.on('data', processMessage);
parser.on('error', sendError);
};
function receive(data)
{
parser.receive(String(data));
}
function processMessage(message)
{
var start = profiler.start();
send(message, function(error, result)
{
if (error)
{
return sendError('Could not run query ' + message.query + ' (' +
message.params + ': ' + error);
}
profiler.measureFrom(start, 'server', 1000);
self.socket.write(protocol.createData(result));
});
}
function sendError(error)
{
log.error('%s', error);
var message = {error: error};
self.socket.write(protocol.createData(message));
self.close();
}
function send(message, callback)
{
var address = message.address || options.address;
if (options.test)
{
address = 'test';
}
pooled.pooledConnect(address, function(error, client, done)
{
if (error)
{
return callback(error);
}
client.query(message.query, message.params, function(error, result)
{
done();
callback(error, result);
});
});
}
function handle(error)
{
log.error('Error %s', error);
self.close();
}
self.close = function()
{
self.socket.end();
};
};
function testConnection(callback)
{
var connection = new Connection({test: true});
var socket = new EventEmitter();
socket.setNoDelay = function() {};
connection.init(socket);
socket.write = function(message)
{
log.debug('Sending %s', message);
connection.close();
};
socket.end = function()
{
testing.success(callback);
};
socket.emit('data', '32\n{"query":"select current_user;"}');
}
function testPartialMessage(callback)
{
var connection = new Connection({test: true});
var socket = new EventEmitter();
socket.setNoDelay = function() {};
connection.init(socket);
socket.write = function(message)
{
log.debug('Sending %s', message);
connection.close();
};
socket.end = function()
{
testing.success(callback);
};
socket.emit('data', '2\n');
socket.emit('data', '{}');
}
/**
* Run package tests.
*/
exports.test = function(callback)
{
var tests = [
testStart,
testConnection,
testPartialMessage,
];
testing.run(tests, callback);
};
// run tests if invoked directly
if (__filename == process.argv[1])
{
log = new Log('debug');
exports.test(testing.show);
}