import env from '#start/env' import logger from '@adonisjs/core/services/logger' import coordinatorConfig from '#config/coordinator' import { XoverClient } from '#lib/nntp/index' /** * The coordinator's header connections (REBUILD.md, decision e: workers * have no NNTP account). At most NNTP_CONNECTIONS, reused; a connection * that failed is closed and replaced on the next use. */ class NntpPool { private idle: XoverClient[] = [] private open = 0 private waiting: ((client: XoverClient) => void)[] = [] get size() { return coordinatorConfig.nntpConnections } async use(work: (client: XoverClient) => Promise): Promise { const client = await this.acquire() let broken = false try { return await work(client) } catch (error) { broken = true throw error } finally { this.release(client, broken) } } private async acquire(): Promise { const ready = this.idle.pop() if (ready) return ready if (this.open < this.size) { this.open++ try { return await XoverClient.connect({ host: env.get('NNTP_HOST')!, port: env.get('NNTP_PORT', 563), user: env.get('NNTP_USER'), password: env.get('NNTP_PASSWORD'), secure: env.get('NNTP_SECURE', true), compress: true, timeoutMs: coordinatorConfig.nntpTimeoutMs, }) } catch (error) { this.open-- throw error } } return new Promise((resolve) => this.waiting.push(resolve)) } private release(client: XoverClient, broken: boolean) { if (broken) { this.open-- try { client.close() } catch (error: any) { logger.debug(`Closing a failed NNTP connection: ${error.message}`) } // Someone waiting gets a fresh connection. const next = this.waiting.shift() if (next) this.acquire().then(next, () => this.waiting.unshift(next)) return } const next = this.waiting.shift() if (next) next(client) else this.idle.push(client) } async close() { for (const client of this.idle.splice(0)) client.close() this.open = 0 } } export default new NntpPool()