Skip to content

Commit 35607ff

Browse files
committed
Fix bug in ConnectionPool._closeQuery
There was a defect which meant that if you closed a prepared query it could end up trying to close the query on a connection which was still in use. Fixes #49
1 parent 62f17bf commit 35607ff

File tree

4 files changed

+58
-6
lines changed

4 files changed

+58
-6
lines changed

lib/src/connection_pool.dart

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ class ConnectionPool extends Object with _ConnectionHelpers implements Queriable
1818
final int _maxPacketSize;
1919
int _max;
2020

21-
/*
21+
/*
2222
* The pool maintains a queue of connection requests. When a connection completes, if there
2323
* is a connection in the queue then it is 'activated' - that is, the future returned
2424
* by _getConnection() completes.
2525
*/
2626
final Queue<Completer<_Connection>> _pendingConnections;
27+
/*
28+
* If you need a particular connection, put an entry in _requestedConnections. As soon as
29+
* that connection is free then the completer completes. _requestedConnections is
30+
* checked before _pendingConnections.
31+
*/
32+
final Map<_Connection, Queue<Completer>> _requestedConnections;
2733
final List<_Connection> _pool;
2834

2935
/**
@@ -40,6 +46,7 @@ class ConnectionPool extends Object with _ConnectionHelpers implements Queriable
4046
// bool useCompression: false,
4147
bool useSSL: false}) :
4248
_pendingConnections = new Queue<Completer<_Connection>>(),
49+
_requestedConnections = new Map<_Connection, Queue<Completer>>(),
4350
_pool = new List<_Connection>(),
4451
_host = host,
4552
_port = port,
@@ -125,6 +132,14 @@ class ConnectionPool extends Object with _ConnectionHelpers implements Queriable
125132
return;
126133
}
127134

135+
if (_requestedConnections.containsKey(cnx) && _requestedConnections[cnx].length > 0) {
136+
_log.finest("Reusing cnx#${cnx.number} for a requested operation");
137+
var c = _requestedConnections[cnx].removeFirst();
138+
cnx.use();
139+
c.complete(cnx);
140+
return;
141+
}
142+
128143
if (_pendingConnections.length > 0) {
129144
_log.finest("Reusing cnx#${cnx.number} for a queued operation");
130145
var c = _pendingConnections.removeFirst();
@@ -217,13 +232,17 @@ class ConnectionPool extends Object with _ConnectionHelpers implements Queriable
217232
}
218233
}
219234

235+
// Close a prepared query on all connections which have this query.
236+
// This may take some time if it has to wait a long time for a
237+
// connection to become free.
220238
_closeQuery(Query q, bool retain) async {
221239
_log.finest("Closing query: ${q.sql}");
222240
var thePool = new List<_Connection>();
223241
thePool.addAll(_pool); // prevent concurrent modification
224242
for (var cnx in thePool) {
225243
var preparedQuery = cnx.removePreparedQueryFromCache(q.sql);
226244
if (preparedQuery != null) {
245+
_log.finest("Connection not ready");
227246
await _waitUntilReady(cnx);
228247
_log.finest("Connection ready - closing query: ${q.sql}");
229248
var handler = new _CloseStatementHandler(preparedQuery.statementHandlerId);
@@ -233,17 +252,22 @@ class ConnectionPool extends Object with _ConnectionHelpers implements Queriable
233252
}
234253
}
235254

236-
/**
237-
* The future returned by [_waitUntilReady] fires when all queued operations in the pool
238-
* have completed, and the connection is free to be used again.
255+
/**
256+
* The future returned by [_waitUntilReady] fires when the connection is next available
257+
* to be used.
239258
*/
240259
Future<_Connection> _waitUntilReady(_Connection cnx) {
241260
var c = new Completer<_Connection>();
242261
if (!cnx.inUse) {
262+
// connection isn't in use, so use it straight away
243263
cnx.use();
244264
c.complete(cnx);
245265
} else {
246-
_pendingConnections.add(c);
266+
// Connection is in use, so request we get it the next time it's available.
267+
if (!_requestedConnections.containsKey(cnx)) {
268+
_requestedConnections[cnx] = new Queue<Completer>();
269+
}
270+
_requestedConnections[cnx].add(c);
247271
}
248272
return c.future;
249273
}

lib/src/query.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Query extends Object with _ConnectionHelpers {
8383
}
8484

8585
/// Closes this query and removes it from all connections in the pool.
86-
void close() {
86+
close() async {
8787
_pool._closeQuery(this, _inTransaction);
8888
}
8989

test/integration/prepared_query.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
part of integrationtests;
2+
3+
void runPreparedQueryTests(String user, String password, String db, int port, String host) {
4+
group('prepared query', ()
5+
{
6+
ConnectionPool pool;
7+
8+
setUp(() {
9+
pool = new ConnectionPool(user:user, password:password, db:db, port:port, host:host, max:2);
10+
return setup(pool, "row",
11+
"create table row (id integer, name text)",
12+
"insert into row values (0, 'One'), (1, 'One')");
13+
});
14+
15+
tearDown(() {
16+
pool.closeConnectionsNow();
17+
});
18+
19+
test('can close prepared query on in-use connections', () async {
20+
var cnx = await pool.getConnection();
21+
var query = await pool.prepare("select * from row");
22+
await query.close();
23+
cnx.release();
24+
});
25+
});
26+
}

test/integration_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ part 'integration/row.dart';
2222
part 'integration/errors.dart';
2323
part 'integration/stored_procedures.dart';
2424
part 'integration/execute_multi.dart';
25+
part 'integration/prepared_query.dart';
2526

2627
void main(List<String> args) {
2728
hierarchicalLoggingEnabled = true;
@@ -51,6 +52,7 @@ void main(List<String> args) {
5152
var db = options.getString('db');
5253
var host = options.getString('host', 'localhost');
5354

55+
runPreparedQueryTests(user, password, db, port, host);
5456
runIntTests(user, password, db, port, host);
5557
runIntTests2(user, password, db, port, host);
5658
runCharsetTests(user, password, db, port, host);

0 commit comments

Comments
 (0)