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:
2026-05-14 02:22:07 +03:00
parent 8fb5dde858
commit 398769ff97
+30
View File
@@ -244,6 +244,8 @@ class Client {
this.requestId = 0;
this._buffer = Buffer.alloc(0);
this._pendingResolve = null;
this._requestQueue = [];
this._requestLock = false;
}
async connect() {
@@ -478,8 +480,31 @@ class Client {
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() {
if (!this.connected) throw new Error('Not connected');
return this._enqueue(async () => {
const msg = this._build(MsgKind.PING, Buffer.alloc(0));
this.socket.write(msg);
@@ -487,10 +512,12 @@ class Client {
if (header.kind === MsgKind.PONG) return true;
if (header.kind === MsgKind.ERROR) throw await this._readError(header.length);
return false;
});
}
async query(sql) {
if (!this.connected) throw new Error('Not connected');
return this._enqueue(async () => {
const queryBuf = Buffer.from(sql, 'utf-8');
const payload = Buffer.alloc(4 + queryBuf.length + 1);
payload.writeUInt32BE(queryBuf.length, 0);
@@ -510,10 +537,12 @@ class Client {
return result;
}
return new QueryResult();
});
}
async queryParams(sql, params = []) {
if (!this.connected) throw new Error('Not connected');
return this._enqueue(async () => {
const queryBuf = Buffer.from(sql, 'utf-8');
const paramParts = [];
for (const p of params) {
@@ -542,6 +571,7 @@ class Client {
return result;
}
return new QueryResult();
});
}
async execute(sql) {