Compare commits

...

6 Commits
v9.0.13 ... 5.3

Author SHA1 Message Date
Girish Ramakrishnan
77785097c1 5.3.4 changes 2020-07-03 14:22:45 -07:00
Girish Ramakrishnan
4991982770 5.3.3 changes
(cherry picked from commit bc6e652293)
2020-07-03 14:22:04 -07:00
Girish Ramakrishnan
f4407f3a43 Fixes for tests
(cherry picked from commit 1c96fbb533)
2020-07-03 14:08:02 -07:00
Girish Ramakrishnan
56a82ef808 database: rework connection logic
(cherry picked from commit 3dc163c33d)
2020-07-03 14:07:54 -07:00
Girish Ramakrishnan
ecce897b5a Fix crash when mysql crashes
(cherry picked from commit d1ff8e9d6b)
2020-07-03 09:58:33 -07:00
Girish Ramakrishnan
c5c8b1e299 database: Fix event emitter warning
the connection object gets reused after release. this means that we keep
attaching the 'error' event and not unlistening.

--trace-warnings can be added to box.service to get the stack trace

(cherry picked from commit 70743bd285)
2020-07-03 09:58:27 -07:00
2 changed files with 37 additions and 81 deletions

View File

@@ -2007,3 +2007,9 @@
* redis: Set maxmemory and maxmemory-policy
* Add mlock capability to manifest (for vault app)
[5.3.3]
* Fix issue where some postinstall messages where causing angular to infinite loop
[5.3.4]
* Fix issue in database error handling

View File

@@ -22,8 +22,7 @@ var assert = require('assert'),
once = require('once'),
util = require('util');
var gConnectionPool = null,
gDefaultConnection = null;
var gConnectionPool = null;
const gDatabase = {
hostname: '127.0.0.1',
@@ -43,66 +42,37 @@ function initialize(callback) {
gDatabase.hostname = require('child_process').execSync('docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" mysql-server').toString().trim();
}
// https://github.com/mysqljs/mysql#pool-options
gConnectionPool = mysql.createPool({
connectionLimit: 5, // this has to be > 1 since we store one connection as 'default'. the rest for transactions
connectionLimit: 5,
host: gDatabase.hostname,
user: gDatabase.username,
password: gDatabase.password,
port: gDatabase.port,
database: gDatabase.name,
multipleStatements: false,
waitForConnections: true, // getConnection() will wait until a connection is avaiable
ssl: false,
timezone: 'Z' // mysql follows the SYSTEM timezone. on Cloudron, this is UTC
});
gConnectionPool.on('connection', function (connection) {
// connection objects are re-used. so we have to attach to the event here (once) to prevent crash
// note the pool also has an 'acquire' event but that is called whenever we do a getConnection()
connection.on('error', (error) => debug(`Connection ${connection.threadId} error: ${error.message} ${error.code}`));
connection.query('USE ' + gDatabase.name);
connection.query('SET SESSION sql_mode = \'strict_all_tables\'');
});
reconnect(callback);
callback(null);
}
function uninitialize(callback) {
if (gConnectionPool) {
gConnectionPool.end(callback);
gDefaultConnection = null;
gConnectionPool = null;
} else {
callback(null);
}
}
if (!gConnectionPool) return callback(null);
function reconnect(callback) {
callback = callback ? once(callback) : function () {};
debug('reconnect: connecting to database');
gConnectionPool.getConnection(function (error, connection) {
if (error) {
debug(`reconnect: db connection error. ${error.message} fatal:${error.fatal} code:${error.code}. Will retry in 10 seconds`);
return setTimeout(reconnect.bind(null, callback), 10000);
}
debug('reconnect: connected to database');
connection.on('error', function (error) {
// by design, we catch all normal errors by providing callbacks.
// this function should be invoked only when we have no callbacks pending and we have a fatal error
assert(error.fatal, 'Non-fatal error on connection object');
debug(`reconnect: db connection error. ${error.message} fatal:${error.fatal} code:${error.code}. Will retry in 10 seconds`);
gDefaultConnection = null;
// This is most likely an issue an can cause double callbacks from reconnect()
setTimeout(reconnect.bind(null, callback), 10000);
});
gDefaultConnection = connection;
callback(null);
});
gConnectionPool.end(callback);
gConnectionPool = null;
}
function clear(callback) {
@@ -115,39 +85,14 @@ function clear(callback) {
child_process.exec(cmd, callback);
}
function beginTransaction(callback) {
assert.strictEqual(typeof callback, 'function');
if (gConnectionPool === null) return callback(new BoxError(BoxError.DATABASE_ERROR, 'No database connection pool.'));
gConnectionPool.getConnection(function (error, connection) {
if (error) return callback(error);
connection.beginTransaction(function (error) {
if (error) return callback(error);
return callback(null, connection);
});
});
}
function query() {
var args = Array.prototype.slice.call(arguments);
var callback = args[args.length - 1];
const args = Array.prototype.slice.call(arguments);
const callback = args[args.length - 1];
assert.strictEqual(typeof callback, 'function');
if (gDefaultConnection === null) return callback(new BoxError(BoxError.DATABASE_ERROR, 'No connection to database'));
if (constants.TEST && !gConnectionPool) return callback(new BoxError(BoxError.DATABASE_ERROR, 'database.js not initialized'));
gDefaultConnection.query.apply(gDefaultConnection, args);
}
function rollback(connection, transactionError, callback) {
connection.rollback(function (error) {
if (error) debug('rollback: error when rolling back', error);
connection.release();
callback(transactionError);
});
gConnectionPool.query.apply(gConnectionPool, args); // this is same as getConnection/query/release
}
function transaction(queries, callback) {
@@ -156,21 +101,26 @@ function transaction(queries, callback) {
callback = once(callback);
beginTransaction(function (error, connection) {
gConnectionPool.getConnection(function (error, connection) {
if (error) return callback(error);
connection.on('error', callback);
const releaseConnection = (error) => { connection.release(); callback(error); };
async.mapSeries(queries, function iterator(query, done) {
connection.query(query.query, query.args, done);
}, function seriesDone(error, results) {
if (error) return rollback(connection, error, callback);
connection.beginTransaction(function (error) {
if (error) return releaseConnection(error);
connection.commit(function (error) {
if (error) return rollback(connection, error, callback);
async.mapSeries(queries, function iterator(query, done) {
connection.query(query.query, query.args, done);
}, function seriesDone(error, results) {
if (error) return connection.rollback(() => releaseConnection(error));
connection.release();
callback(null, results);
connection.commit(function (error) {
if (error) return connection.rollback(() => releaseConnection(error));
connection.release();
callback(null, results);
});
});
});
});