Skip to content

Commit 1e612dc

Browse files
authored
feat: add Symbol.dispose and Symbol.asyncDispose support for Connections, Pools, and Pool Clusters (#4112)
* feat: support `using`, `dispose`, and `asyncDispose` * chore: implement types * ci: add tests * chore: implement `dispose` for Pool Cluster * refactor: improve test organization * refactor: force `using` keyword * docs: add docs for all types of connections (including how to end them)
1 parent a3178bf commit 1e612dc

24 files changed

+2226
-47
lines changed

lib/base/connection.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,12 @@ class BaseConnection extends EventEmitter {
926926
return this.addCommand(new Commands.ServerHandshake(args));
927927
}
928928

929+
[Symbol.dispose]() {
930+
if (!this._closing) {
931+
this.end();
932+
}
933+
}
934+
929935
// ===============================================================
930936
end(callback) {
931937
if (this.config.isServer) {

lib/base/pool.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class BasePool extends EventEmitter {
108108
}
109109
}
110110

111+
[Symbol.dispose]() {
112+
if (!this._closed) {
113+
this.end();
114+
}
115+
}
116+
111117
end(cb) {
112118
this._closed = true;
113119
clearTimeout(this._removeIdleTimeoutConnectionsTimer);

lib/base/pool_connection.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class BasePoolConnection extends BaseConnection {
2929
this._pool.releaseConnection(this);
3030
}
3131

32+
[Symbol.dispose]() {
33+
this.release();
34+
}
35+
3236
end(callback) {
3337
if (this.config.gracefulEnd) {
3438
this._removeFromPool();

lib/pool_cluster.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ class PoolCluster extends EventEmitter {
229229
namespace.getConnection(cb);
230230
}
231231

232+
[Symbol.dispose]() {
233+
if (!this._closed) {
234+
this.end();
235+
}
236+
}
237+
232238
end(callback) {
233239
const cb =
234240
callback !== undefined

lib/promise/connection.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ class PromiseConnection extends EventEmitter {
6666
});
6767
}
6868

69+
async [Symbol.asyncDispose]() {
70+
if (!this.connection._closing) {
71+
await this.end();
72+
}
73+
}
74+
6975
beginTransaction() {
7076
const c = this.connection;
7177
const localErr = new Error();

lib/promise/pool.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ class PromisePool extends EventEmitter {
8585
});
8686
});
8787
}
88+
89+
async [Symbol.asyncDispose]() {
90+
if (!this.pool._closed) {
91+
await this.end();
92+
}
93+
}
8894
}
8995

9096
(function (functionsToWrap) {

lib/promise/pool_connection.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ class PromisePoolConnection extends PromiseConnection {
1414
arguments
1515
);
1616
}
17+
18+
async [Symbol.asyncDispose]() {
19+
this.release();
20+
}
1721
}
1822

1923
module.exports = PromisePoolConnection;

promise.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export interface Connection extends QueryableAndExecutableBase {
6565

6666
end(options?: any): Promise<void>;
6767

68+
[Symbol.asyncDispose](): Promise<void>;
69+
6870
destroy(): void;
6971

7072
pause(): void;
@@ -82,6 +84,7 @@ export interface Connection extends QueryableAndExecutableBase {
8284
export interface PoolConnection extends Connection {
8385
release(): void;
8486
connection: Connection;
87+
[Symbol.asyncDispose](): Promise<void>;
8588
}
8689

8790
export interface Pool extends Connection {
@@ -112,6 +115,8 @@ export interface PoolCluster extends EventEmitter {
112115

113116
end(): Promise<void>;
114117

118+
[Symbol.asyncDispose](): Promise<void>;
119+
115120
getConnection(): Promise<PoolConnection>;
116121
getConnection(group: string): Promise<PoolConnection>;
117122
getConnection(group: string, selector: string): Promise<PoolConnection>;

promise.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ class PromisePoolCluster extends EventEmitter {
134134
});
135135
});
136136
}
137+
138+
async [Symbol.asyncDispose]() {
139+
if (!this.poolCluster._closed) {
140+
await this.end();
141+
}
142+
}
137143
}
138144

139145
/**
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { RowDataPacket } from '../../../../index.js';
2+
import { assert, describe, it, skip } from 'poku';
3+
import { createConnection } from '../../../../promise.js';
4+
import { config } from '../../../common.test.mjs';
5+
6+
if (!('asyncDispose' in Symbol)) {
7+
skip('Symbol.asyncDispose is not supported in this runtime');
8+
}
9+
10+
await describe('PromiseConnection should implement Symbol.asyncDispose', async () => {
11+
await using conn = await createConnection(config);
12+
13+
it('should be a function', () => {
14+
assert.strictEqual(typeof conn[Symbol.asyncDispose], 'function');
15+
});
16+
});
17+
18+
await describe('asyncDispose should end the connection', async () => {
19+
await using conn = await createConnection(config);
20+
const [rows] = await conn.query<RowDataPacket[]>('SELECT 1');
21+
22+
await conn[Symbol.asyncDispose]();
23+
24+
it('should have received the query result', () => {
25+
assert.deepStrictEqual(rows, [{ 1: 1 }]);
26+
});
27+
28+
it('should have closed the connection', () => {
29+
// @ts-expect-error: internal access
30+
assert.strictEqual(conn.connection._closing, true);
31+
});
32+
});
33+
34+
await describe('asyncDispose should handle manual destroy before asyncDispose on connection', async () => {
35+
await using conn = await createConnection(config);
36+
const [rows] = await conn.query<RowDataPacket[]>('SELECT 1');
37+
38+
conn.destroy();
39+
await conn[Symbol.asyncDispose]();
40+
41+
it('should have received the query result', () => {
42+
assert.deepStrictEqual(rows, [{ 1: 1 }]);
43+
});
44+
45+
it('should have closed the connection', () => {
46+
// @ts-expect-error: internal access
47+
assert.strictEqual(conn.connection._closing, true);
48+
});
49+
});

0 commit comments

Comments
 (0)