feat(client): add TCP request queue for safe concurrency
The previous implementation allowed multiple concurrent async calls (query, execute, ping) to interleave writes on the same socket, which corrupted the binary protocol framing when NodeBB fired parallel database operations. Add an internal _requestQueue and _requestLock so that all TCP requests are serialized: each async operation enqueues a task, and tasks are drained one at a time via setImmediate().
This commit is contained in:
@@ -244,6 +244,8 @@ class Client {
|
|||||||
this.requestId = 0;
|
this.requestId = 0;
|
||||||
this._buffer = Buffer.alloc(0);
|
this._buffer = Buffer.alloc(0);
|
||||||
this._pendingResolve = null;
|
this._pendingResolve = null;
|
||||||
|
this._requestQueue = [];
|
||||||
|
this._requestLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
@@ -478,8 +480,31 @@ class Client {
|
|||||||
throw new Error(`Unexpected auth response: 0x${header.kind.toString(16)}`);
|
throw new Error(`Unexpected auth response: 0x${header.kind.toString(16)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _processQueue() {
|
||||||
|
if (this._requestLock || this._requestQueue.length === 0) return;
|
||||||
|
this._requestLock = true;
|
||||||
|
const { task, resolve, reject } = this._requestQueue.shift();
|
||||||
|
try {
|
||||||
|
const result = await task();
|
||||||
|
resolve(result);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
} finally {
|
||||||
|
this._requestLock = false;
|
||||||
|
setImmediate(() => this._processQueue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueue(task) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._requestQueue.push({ task, resolve, reject });
|
||||||
|
this._processQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async ping() {
|
async ping() {
|
||||||
if (!this.connected) throw new Error('Not connected');
|
if (!this.connected) throw new Error('Not connected');
|
||||||
|
return this._enqueue(async () => {
|
||||||
const msg = this._build(MsgKind.PING, Buffer.alloc(0));
|
const msg = this._build(MsgKind.PING, Buffer.alloc(0));
|
||||||
this.socket.write(msg);
|
this.socket.write(msg);
|
||||||
|
|
||||||
@@ -487,10 +512,12 @@ class Client {
|
|||||||
if (header.kind === MsgKind.PONG) return true;
|
if (header.kind === MsgKind.PONG) return true;
|
||||||
if (header.kind === MsgKind.ERROR) throw await this._readError(header.length);
|
if (header.kind === MsgKind.ERROR) throw await this._readError(header.length);
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(sql) {
|
async query(sql) {
|
||||||
if (!this.connected) throw new Error('Not connected');
|
if (!this.connected) throw new Error('Not connected');
|
||||||
|
return this._enqueue(async () => {
|
||||||
const queryBuf = Buffer.from(sql, 'utf-8');
|
const queryBuf = Buffer.from(sql, 'utf-8');
|
||||||
const payload = Buffer.alloc(4 + queryBuf.length + 1);
|
const payload = Buffer.alloc(4 + queryBuf.length + 1);
|
||||||
payload.writeUInt32BE(queryBuf.length, 0);
|
payload.writeUInt32BE(queryBuf.length, 0);
|
||||||
@@ -510,10 +537,12 @@ class Client {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return new QueryResult();
|
return new QueryResult();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryParams(sql, params = []) {
|
async queryParams(sql, params = []) {
|
||||||
if (!this.connected) throw new Error('Not connected');
|
if (!this.connected) throw new Error('Not connected');
|
||||||
|
return this._enqueue(async () => {
|
||||||
const queryBuf = Buffer.from(sql, 'utf-8');
|
const queryBuf = Buffer.from(sql, 'utf-8');
|
||||||
const paramParts = [];
|
const paramParts = [];
|
||||||
for (const p of params) {
|
for (const p of params) {
|
||||||
@@ -542,6 +571,7 @@ class Client {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return new QueryResult();
|
return new QueryResult();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(sql) {
|
async execute(sql) {
|
||||||
|
|||||||
Reference in New Issue
Block a user