Skip to content

Commit 772bbbd

Browse files
authored
Merge pull request #1364 from yuxizhe/feat-query-timeout
feat(query): add timeout option
2 parents 4e22e33 + 1b542f8 commit 772bbbd

File tree

5 files changed

+126
-1
lines changed

5 files changed

+126
-1
lines changed

lib/commands/execute.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class Execute extends Command {
1515
this.onResult = callback;
1616
this.parameters = options.values;
1717
this.insertId = 0;
18+
this.timeout = options.timeout;
19+
this.queryTimeout = null;
1820
this._rows = [];
1921
this._fields = [];
2022
this._result = [];
@@ -35,6 +37,7 @@ class Execute extends Command {
3537
start(packet, connection) {
3638
this._connection = connection;
3739
this.options = Object.assign({}, connection.config, this._executeOptions);
40+
this._setTimeout();
3841
const executePacket = new Packets.Execute(
3942
this.statement.id,
4043
this.parameters,
@@ -96,6 +99,8 @@ Execute.prototype.resultsetHeader = Query.prototype.resultsetHeader;
9699
Execute.prototype._findOrCreateReadStream =
97100
Query.prototype._findOrCreateReadStream;
98101
Execute.prototype._streamLocalInfile = Query.prototype._streamLocalInfile;
102+
Execute.prototype._setTimeout = Query.prototype._setTimeout;
103+
Execute.prototype._handleTimeoutError = Query.prototype._handleTimeoutError;
99104
Execute.prototype.row = Query.prototype.row;
100105
Execute.prototype.stream = Query.prototype.stream;
101106

lib/commands/query.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const process = require('process');
4+
const Timers = require('timers');
45

56
const Readable = require('stream').Readable;
67

@@ -21,6 +22,8 @@ class Query extends Command {
2122
this._queryOptions = options;
2223
this.namedPlaceholders = options.namedPlaceholders || false;
2324
this.onResult = callback;
25+
this.timeout = options.timeout;
26+
this.queryTimeout = null;
2427
this._fieldCount = 0;
2528
this._rowParser = null;
2629
this._fields = [];
@@ -48,6 +51,8 @@ class Query extends Command {
4851
}
4952
this._connection = connection;
5053
this.options = Object.assign({}, connection.config, this._queryOptions);
54+
this._setTimeout();
55+
5156
const cmdPacket = new Packets.Query(
5257
this.sql,
5358
connection.config.charsetNumber
@@ -58,6 +63,15 @@ class Query extends Command {
5863

5964
done() {
6065
this._unpipeStream();
66+
// if all ready timeout, return null directly
67+
if (this.timeout && !this.queryTimeout) {
68+
return null;
69+
}
70+
// else clear timer
71+
if (this.queryTimeout) {
72+
Timers.clearTimeout(this.queryTimeout);
73+
this.queryTimeout = null;
74+
}
6175
if (this.onResult) {
6276
let rows, fields;
6377
if (this._resultIndex === 0) {
@@ -272,6 +286,34 @@ class Query extends Command {
272286
});
273287
return stream;
274288
}
289+
290+
_setTimeout() {
291+
if (this.timeout) {
292+
const timeoutHandler = this._handleTimeoutError.bind(this);
293+
this.queryTimeout = Timers.setTimeout(
294+
timeoutHandler,
295+
this.timeout
296+
);
297+
}
298+
}
299+
300+
_handleTimeoutError() {
301+
if (this.queryTimeout) {
302+
Timers.clearTimeout(this.queryTimeout);
303+
this.queryTimeout = null;
304+
}
305+
306+
const err = new Error('Query inactivity timeout');
307+
err.errorno = 'PROTOCOL_SEQUENCE_TIMEOUT';
308+
err.code = 'PROTOCOL_SEQUENCE_TIMEOUT';
309+
err.syscall = 'query';
310+
311+
if (this.onResult) {
312+
this.onResult(err);
313+
} else {
314+
this.emit('error', err);
315+
}
316+
}
275317
}
276318

277319
Query.prototype.catch = Query.prototype.then;

test/common.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ exports.createConnection = function(args) {
121121
dateStrings: args && args.dateStrings,
122122
authSwitchHandler: args && args.authSwitchHandler,
123123
typeCast: args && args.typeCast,
124-
namedPlaceholders: args && args.namedPlaceholders
124+
namedPlaceholders: args && args.namedPlaceholders,
125+
connectTimeout: args && args.connectTimeout,
125126
};
126127

127128
const conn = driver.createConnection(params);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const connection = common.createConnection({
5+
host: '10.255.255.1',
6+
debug: false,
7+
connectTimeout: 100,
8+
});
9+
10+
const assert = require('assert');
11+
12+
connection.query('SELECT sleep(3) as a', (err, res) => {
13+
assert.equal(res, null);
14+
assert.ok(err);
15+
assert.equal(err.code, 'ETIMEDOUT');
16+
assert.equal(err.message, 'connect ETIMEDOUT');
17+
});
18+
19+
connection.query({ sql: 'SELECT sleep(3) as a' , timeout: 50}, (err, res) => {
20+
assert.equal(res, null);
21+
assert.ok(err);
22+
assert.equal(err.code, 'ETIMEDOUT');
23+
assert.equal(err.message, 'connect ETIMEDOUT');
24+
});
25+
26+
27+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const connection = common.createConnection({ debug: false });
5+
const assert = require('assert');
6+
7+
connection.query({ sql: 'SELECT sleep(3) as a', timeout: 500 }, (err, res) => {
8+
assert.equal(res, null);
9+
assert.ok(err);
10+
assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT');
11+
assert.equal(err.message, 'Query inactivity timeout');
12+
});
13+
14+
connection.query({ sql: 'SELECT sleep(1) as a', timeout: 5000 }, (err, res) => {
15+
assert.deepEqual(res, [{ a: 0 }]);
16+
});
17+
18+
connection.query('SELECT sleep(1) as a', (err, res) => {
19+
assert.deepEqual(res, [{ a: 0 }]);
20+
});
21+
22+
connection.execute({ sql: 'SELECT sleep(3) as a', timeout: 500 }, (err, res) => {
23+
assert.equal(res, null);
24+
assert.ok(err);
25+
assert.equal(err.code, 'PROTOCOL_SEQUENCE_TIMEOUT');
26+
assert.equal(err.message, 'Query inactivity timeout');
27+
});
28+
29+
connection.execute({ sql: 'SELECT sleep(1) as a', timeout: 5000 }, (err, res) => {
30+
assert.deepEqual(res, [{ a: 0 }]);
31+
});
32+
33+
connection.execute('SELECT sleep(1) as a', (err, res) => {
34+
assert.deepEqual(res, [{ a: 0 }]);
35+
connection.end();
36+
});
37+
38+
const connectionTimeout = common.createConnection({
39+
host: '10.255.255.1',
40+
debug: false,
41+
connectTimeout: 100,
42+
});
43+
44+
// return connect timeout error first
45+
connectionTimeout.query({ sql: 'SELECT sleep(3) as a', timeout: 50 }, (err, res) => {
46+
assert.equal(res, null);
47+
assert.ok(err);
48+
assert.equal(err.code, 'ETIMEDOUT');
49+
assert.equal(err.message, 'connect ETIMEDOUT');
50+
});

0 commit comments

Comments
 (0)