implemented a lot of features
This commit is contained in:
1 parent
62f1767df3
commit
bcb9be5da9
141 files changed
+10705
-385
No files matched your search
+68
-21
@@ -1,5 +1,6 @@
|
||||
import nntpConfig from '#config/nntp'
|
||||
import { NNTP } from "nntp-js";
|
||||
import { parseGroupResponse, type GroupInfo } from '#lib/nntp/group'
|
||||
|
||||
class NntpService {
|
||||
private readonly poolSize: number;
|
||||
@@ -14,15 +15,20 @@ class NntpService {
|
||||
this.idleConnections = [];
|
||||
this.waiters = [];
|
||||
this.createdCount = 0;
|
||||
console.log(`NNTP Pool initialized with size ${this.poolSize}`)
|
||||
}
|
||||
|
||||
private async _createConnection() {
|
||||
// This connection logic is based on the older, working pool implementation.
|
||||
const conn = new NNTP(nntpConfig.host, nntpConfig.port);
|
||||
await conn.connect();
|
||||
if (nntpConfig.user) {
|
||||
await conn.login(nntpConfig.user, nntpConfig.password?.release());
|
||||
try {
|
||||
await conn.connect();
|
||||
if (nntpConfig.user) {
|
||||
await conn.login(nntpConfig.user, nntpConfig.password?.release());
|
||||
}
|
||||
} catch (error) {
|
||||
// Free the slot, otherwise a failed connect shrinks the pool for good.
|
||||
this.createdCount--;
|
||||
this._destroy(conn);
|
||||
throw error;
|
||||
}
|
||||
this.allConnections.add(conn);
|
||||
return conn;
|
||||
@@ -30,38 +36,79 @@ class NntpService {
|
||||
|
||||
public async acquire() {
|
||||
if (this.idleConnections.length > 0) {
|
||||
console.log('Reusing existing connection from pool.');
|
||||
return this.idleConnections.pop();
|
||||
}
|
||||
|
||||
if (this.createdCount < this.poolSize) {
|
||||
this.createdCount++;
|
||||
console.log(`Creating new connection (${this.createdCount}/${this.poolSize}).`);
|
||||
console.log(`Creating new connection (${this.createdCount}/${this.poolSize}).`);
|
||||
return this._createConnection();
|
||||
}
|
||||
|
||||
console.log(`Pool maxed out at ${this.poolSize}. Waiting for a connection to become available.`);
|
||||
return new Promise(resolve => this.waiters.push(resolve));
|
||||
}
|
||||
|
||||
public release(conn: any) {
|
||||
if (this.waiters.length > 0) {
|
||||
console.log('Releasing connection directly to a waiting task.');
|
||||
const resolve = this.waiters.shift();
|
||||
if(resolve) resolve(conn);
|
||||
/**
|
||||
* Returns a connection to the pool. Pass `failed` when a command on it
|
||||
* threw: the connection may be dead (e.g. closed by the server while idle),
|
||||
* so it is discarded instead of being handed to the next job.
|
||||
*/
|
||||
public release(conn: any, failed = false) {
|
||||
if (failed) {
|
||||
this.discard(conn);
|
||||
return;
|
||||
}
|
||||
const resolve = this.waiters.shift();
|
||||
if (resolve) {
|
||||
resolve(conn);
|
||||
} else {
|
||||
console.log('Returning connection to the idle pool.');
|
||||
this.idleConnections.push(conn);
|
||||
}
|
||||
}
|
||||
|
||||
public async shutdown() {
|
||||
console.log('Shutting down all connections in the pool.');
|
||||
const shutdownPromises: Promise<any>[] = [];
|
||||
for (const conn of this.allConnections) {
|
||||
shutdownPromises.push(conn.quit());
|
||||
/** Closes a broken connection and frees its slot for a new one. */
|
||||
public discard(conn: any) {
|
||||
this.allConnections.delete(conn);
|
||||
this.createdCount--;
|
||||
this._destroy(conn);
|
||||
|
||||
// Someone is waiting for a slot: open a fresh connection for them.
|
||||
const resolve = this.waiters.shift();
|
||||
if (resolve) {
|
||||
this.createdCount++;
|
||||
this._createConnection().then(resolve, () => this.waiters.unshift(resolve));
|
||||
}
|
||||
await Promise.all(shutdownPromises);
|
||||
}
|
||||
|
||||
private _destroy(conn: any) {
|
||||
try {
|
||||
conn.sock?.destroy();
|
||||
} catch {
|
||||
// already closed
|
||||
}
|
||||
}
|
||||
|
||||
/** Selects a group and returns its correctly parsed article range. */
|
||||
public async selectGroup(conn: any, name: string): Promise<GroupInfo> {
|
||||
const { resp } = await conn.group(name)
|
||||
return parseGroupResponse(resp)
|
||||
}
|
||||
|
||||
/** Fetches a raw (still dot-stuffed) article body as a Buffer. */
|
||||
public async fetchBody(conn: any, messageId: string): Promise<Buffer> {
|
||||
const id = messageId.startsWith('<') ? messageId : `<${messageId}>`
|
||||
const { data } = await conn.body(id)
|
||||
return Buffer.from(data.buffer, data.byteOffset, data.byteLength)
|
||||
}
|
||||
|
||||
public async shutdown() {
|
||||
console.log('Shutting down all connections in the pool.');
|
||||
// A connection the server already closed may never answer QUIT.
|
||||
const quit = (conn: any) =>
|
||||
Promise.race([conn.quit(), new Promise((resolve) => setTimeout(resolve, 2000).unref())])
|
||||
.catch(() => {})
|
||||
.finally(() => this._destroy(conn));
|
||||
await Promise.all([...this.allConnections].map(quit));
|
||||
this.allConnections.clear();
|
||||
this.idleConnections.length = 0;
|
||||
this.waiters.length = 0;
|
||||
@@ -69,4 +116,4 @@ class NntpService {
|
||||
}
|
||||
}
|
||||
|
||||
export default new NntpService();
|
||||
export default new NntpService(nntpConfig.poolSize);
|
||||
Reference in new issue
Block a user