不要怂,就是干,撸起袖子干!

Commit 6388507e by Pedro Augusto de Paula Barbosa Committed by GitHub

fix(mysql): release connection on deadlocks (#13102)

* test(mysql, mariadb): improve transaction tests

- Greatly improve test for `SELECT ... LOCK IN SHARE MODE`
- Greatly improve test for deadlock handling

* fix(mysql): release connection on deadlocks

This is a follow-up for a problem not covered by #12841.

* refactor(mariadb): `query.js` similar to mysql's

* Update comments with a reference to this PR
1 parent ced4dc78
......@@ -7,6 +7,7 @@ const DataTypes = require('../../data-types');
const { logger } = require('../../utils/logger');
const ER_DUP_ENTRY = 1062;
const ER_DEADLOCK = 1213;
const ER_ROW_IS_REFERENCED = 1451;
const ER_NO_REFERENCED_ROW = 1452;
......@@ -46,40 +47,25 @@ class Query extends AbstractQuery {
try {
results = await connection.query(this.sql, parameters);
complete();
// Log warnings if we've got them.
if (showWarnings && results && results.warningStatus > 0) {
await this.logWarnings(results);
}
} catch (err) {
// MariaDB automatically rolls-back transactions in the event of a
// deadlock.
//
// Even though we shouldn't need to do this, we initiate a manual
// rollback. Without the rollback, the next transaction using the
// connection seems to retain properties of the previous transaction
// (e.g. isolation level) and not work as expected.
//
// For example (in our tests), a follow-up READ_COMMITTED transaction
// doesn't work as expected unless we explicitly rollback the
// transaction: it would fail to read a value inserted outside of that
// transaction.
if (options.transaction && err.errno === 1213) {
} catch (error) {
if (options.transaction && error.errno === ER_DEADLOCK) {
// MariaDB automatically rolls-back transactions in the event of a deadlock.
// However, we still initiate a manual rollback to ensure the connection gets released - see #13102.
try {
await options.transaction.rollback();
} catch (err) {
} catch (error_) {
// Ignore errors - since MariaDB automatically rolled back, we're
// not that worried about this redundant rollback failing.
}
options.transaction.finished = 'rollback';
}
error.sql = sql;
error.parameters = parameters;
throw this.formatError(error);
} finally {
complete();
err.sql = sql;
err.parameters = parameters;
throw this.formatError(err);
}
if (showWarnings && results && results.warningStatus > 0) {
......
......@@ -6,6 +6,7 @@ const _ = require('lodash');
const { logger } = require('../../utils/logger');
const ER_DUP_ENTRY = 1062;
const ER_DEADLOCK = 1213;
const ER_ROW_IS_REFERENCED = 1451;
const ER_NO_REFERENCED_ROW = 1452;
......@@ -57,19 +58,27 @@ class Query extends AbstractQuery {
.setMaxListeners(100);
});
}
} catch (err) {
// MySQL automatically rolls-back transactions in the event of a deadlock
if (options.transaction && err.errno === 1213) {
} catch (error) {
if (options.transaction && error.errno === ER_DEADLOCK) {
// MySQL automatically rolls-back transactions in the event of a deadlock.
// However, we still initiate a manual rollback to ensure the connection gets released - see #13102.
try {
await options.transaction.rollback();
} catch (error_) {
// Ignore errors - since MySQL automatically rolled back, we're
// not that worried about this redundant rollback failing.
}
options.transaction.finished = 'rollback';
}
err.sql = sql;
err.parameters = parameters;
throw this.formatError(err);
error.sql = sql;
error.parameters = parameters;
throw this.formatError(error);
} finally {
complete();
}
complete();
if (showWarnings && results && results.warningStatus > 0) {
await this.logWarnings(results);
}
......
......@@ -80,6 +80,7 @@
"nyc": "^15.0.0",
"p-map": "^4.0.0",
"p-props": "^4.0.0",
"p-settle": "^4.1.1",
"p-timeout": "^4.0.0",
"pg": "^8.2.1",
"pg-hstore": "^2.x",
......
......@@ -18,8 +18,8 @@ describe(Support.getTestDialectTeaser('Replication'), () => {
this.sequelize = Support.getSequelizeInstance(null, null, null, {
replication: {
write: Support.getConnectionOptions(),
read: [Support.getConnectionOptions()]
write: Support.getConnectionOptionsWithoutPool(),
read: [Support.getConnectionOptionsWithoutPool()]
}
});
......
......@@ -2,6 +2,7 @@
const fs = require('fs');
const path = require('path');
const { isDeepStrictEqual } = require('util');
const _ = require('lodash');
const Sequelize = require('../index');
const Config = require('./config/config');
......@@ -119,11 +120,10 @@ const Support = {
return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions);
},
getConnectionOptions() {
const config = Config[this.getTestDialect()];
getConnectionOptionsWithoutPool() {
// Do not break existing config object - shallow clone before `delete config.pool`
const config = { ...Config[this.getTestDialect()] };
delete config.pool;
return config;
},
......@@ -207,6 +207,10 @@ const Support = {
return `[${dialect.toUpperCase()}] ${moduleName}`;
},
getPoolMax() {
return Config[this.getTestDialect()].pool.max;
},
expectsql(query, assertions) {
const expectations = assertions.query || assertions;
let expectation = expectations[Support.sequelize.dialect.name];
......@@ -234,6 +238,10 @@ const Support = {
const bind = assertions.bind[Support.sequelize.dialect.name] || assertions.bind['default'] || assertions.bind;
expect(query.bind).to.deep.equal(bind);
}
},
isDeepEqualToOneOf(actual, expectedOptions) {
return expectedOptions.some(expected => isDeepStrictEqual(actual, expected));
}
};
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!