mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
prevent half connections staying open
This commit is contained in:
@@ -19,6 +19,8 @@ STRATUM_PORTS=3333,3332,3331,3330
|
|||||||
|
|
||||||
STRATUM_SECURE=true
|
STRATUM_SECURE=true
|
||||||
SECURE_STRATUM_PORTS=4333,4332,4331,4330
|
SECURE_STRATUM_PORTS=4333,4332,4331,4330
|
||||||
|
STRATUM_MAX_CONNECTIONS_PER_LISTENER=10000
|
||||||
|
STRATUM_TLS_HANDSHAKE_TIMEOUT_MS=10000
|
||||||
|
|
||||||
#optional telegram bot
|
#optional telegram bot
|
||||||
#TELEGRAM_BOT_TOKEN=
|
#TELEGRAM_BOT_TOKEN=
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
# Docker build environment #
|
# Docker build environment #
|
||||||
############################
|
############################
|
||||||
|
|
||||||
FROM node:18.16.1-bookworm-slim AS build
|
FROM node:24.16.0-bookworm-slim AS build
|
||||||
|
|
||||||
# Upgrade all packages and install dependencies
|
# Upgrade all packages and install dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
@@ -24,7 +24,7 @@ RUN npm i && npm run build
|
|||||||
# Docker final environment #
|
# Docker final environment #
|
||||||
############################
|
############################
|
||||||
|
|
||||||
FROM node:18.16.1-bookworm-slim
|
FROM node:24.16.0-bookworm-slim
|
||||||
|
|
||||||
# Expose ports for Stratum and Bitcoin RPC
|
# Expose ports for Stratum and Bitcoin RPC
|
||||||
EXPOSE 3333 3334 8332
|
EXPOSE 3333 3334 8332
|
||||||
|
|||||||
@@ -45,6 +45,20 @@ Install pm2 (https://pm2.keymetrics.io/)
|
|||||||
$ pm2 start dist/main.js
|
$ pm2 start dist/main.js
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When running the worker app in PM2 cluster mode, start the PM2 daemon with OS-level
|
||||||
|
connection scheduling. The environment variable must be present when the PM2 daemon
|
||||||
|
starts, not only in the worker configuration.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ NODE_CLUSTER_SCHED_POLICY=none pm2 start ecosystem.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Cluster-mode connection dropping requires Node.js `22.12.0` or newer.
|
||||||
|
|
||||||
|
`STRATUM_MAX_CONNECTIONS_PER_LISTENER` is enforced per worker and Stratum port.
|
||||||
|
Size it using the busiest port: `worker count * limit`. For example, 28 workers
|
||||||
|
with the default limit of `10000` allow up to `280000` connections on one port.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
Build container:
|
Build container:
|
||||||
|
|||||||
Generated
+3
@@ -9,6 +9,9 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/axios": "^4.0.1",
|
"@nestjs/axios": "^4.0.1",
|
||||||
"@nestjs/cache-manager": "^3.1.2",
|
"@nestjs/cache-manager": "^3.1.2",
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ describe('StratumV1Service', () => {
|
|||||||
const originalStratumSecure = process.env.STRATUM_SECURE;
|
const originalStratumSecure = process.env.STRATUM_SECURE;
|
||||||
const originalSecureStratumPorts = process.env.SECURE_STRATUM_PORTS;
|
const originalSecureStratumPorts = process.env.SECURE_STRATUM_PORTS;
|
||||||
const originalBackpressureEnabled = process.env.STRATUM_BACKPRESSURE_ENABLED;
|
const originalBackpressureEnabled = process.env.STRATUM_BACKPRESSURE_ENABLED;
|
||||||
|
const originalMaxConnectionsPerListener = process.env.STRATUM_MAX_CONNECTIONS_PER_LISTENER;
|
||||||
|
const originalTlsHandshakeTimeoutMs = process.env.STRATUM_TLS_HANDSHAKE_TIMEOUT_MS;
|
||||||
|
|
||||||
let service: StratumV1Service;
|
let service: StratumV1Service;
|
||||||
let clientService;
|
let clientService;
|
||||||
@@ -37,6 +39,8 @@ describe('StratumV1Service', () => {
|
|||||||
restoreEnv('STRATUM_SECURE', originalStratumSecure);
|
restoreEnv('STRATUM_SECURE', originalStratumSecure);
|
||||||
restoreEnv('SECURE_STRATUM_PORTS', originalSecureStratumPorts);
|
restoreEnv('SECURE_STRATUM_PORTS', originalSecureStratumPorts);
|
||||||
restoreEnv('STRATUM_BACKPRESSURE_ENABLED', originalBackpressureEnabled);
|
restoreEnv('STRATUM_BACKPRESSURE_ENABLED', originalBackpressureEnabled);
|
||||||
|
restoreEnv('STRATUM_MAX_CONNECTIONS_PER_LISTENER', originalMaxConnectionsPerListener);
|
||||||
|
restoreEnv('STRATUM_TLS_HANDSHAKE_TIMEOUT_MS', originalTlsHandshakeTimeoutMs);
|
||||||
consoleLogSpy.mockRestore();
|
consoleLogSpy.mockRestore();
|
||||||
consoleWarnSpy.mockRestore();
|
consoleWarnSpy.mockRestore();
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
@@ -119,6 +123,28 @@ describe('StratumV1Service', () => {
|
|||||||
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Resuming Stratum accepts'));
|
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Resuming Stratum accepts'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should cap listener connections and drop excess cluster connections', () => {
|
||||||
|
process.env.STRATUM_MAX_CONNECTIONS_PER_LISTENER = '250';
|
||||||
|
const server = {} as any;
|
||||||
|
|
||||||
|
(service as any).configureConnectionLimit(server);
|
||||||
|
|
||||||
|
expect(server.maxConnections).toBe(250);
|
||||||
|
expect(server.dropMaxConnection).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow high-volume pools by default', () => {
|
||||||
|
delete process.env.STRATUM_MAX_CONNECTIONS_PER_LISTENER;
|
||||||
|
|
||||||
|
expect((service as any).getMaxConnectionsPerListener()).toBe(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use an explicit TLS handshake timeout', () => {
|
||||||
|
process.env.STRATUM_TLS_HANDSHAKE_TIMEOUT_MS = '5000';
|
||||||
|
|
||||||
|
expect((service as any).getTlsHandshakeTimeoutMs()).toBe(5000);
|
||||||
|
});
|
||||||
|
|
||||||
function restoreEnv(key: string, value: string | undefined) {
|
function restoreEnv(key: string, value: string | undefined) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
delete process.env[key];
|
delete process.env[key];
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const DEFAULT_BACKPRESSURE_EVENT_LOOP_RESUME_P95_MS = 250;
|
|||||||
const DEFAULT_BACKPRESSURE_RSS_MB = 2500;
|
const DEFAULT_BACKPRESSURE_RSS_MB = 2500;
|
||||||
const DEFAULT_BACKPRESSURE_RESUME_RSS_MB = 2000;
|
const DEFAULT_BACKPRESSURE_RESUME_RSS_MB = 2000;
|
||||||
const DEFAULT_BACKPRESSURE_HEALTHY_CHECKS = 3;
|
const DEFAULT_BACKPRESSURE_HEALTHY_CHECKS = 3;
|
||||||
|
const DEFAULT_MAX_CONNECTIONS_PER_LISTENER = 10000;
|
||||||
|
const DEFAULT_TLS_HANDSHAKE_TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -162,6 +164,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
server.on('error', (err) => {
|
server.on('error', (err) => {
|
||||||
console.error(`Server error: ${err.message}`);
|
console.error(`Server error: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
this.configureConnectionLimit(server);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
@@ -186,6 +189,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
const tlsOptions: TlsOptions = {
|
const tlsOptions: TlsOptions = {
|
||||||
key: readFileSync(keyPath),
|
key: readFileSync(keyPath),
|
||||||
cert: readFileSync(certPath),
|
cert: readFileSync(certPath),
|
||||||
|
handshakeTimeout: this.getTlsHandshakeTimeoutMs()
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = createServer(tlsOptions, async (socket: TLSSocket) => {
|
const server = createServer(tlsOptions, async (socket: TLSSocket) => {
|
||||||
@@ -242,6 +246,7 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
server.on('error', (err) => {
|
server.on('error', (err) => {
|
||||||
console.error(`Server error: ${err.message}`);
|
console.error(`Server error: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
this.configureConnectionLimit(server);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
|
|
||||||
@@ -371,6 +376,19 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_HEALTHY_CHECKS', DEFAULT_BACKPRESSURE_HEALTHY_CHECKS);
|
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_HEALTHY_CHECKS', DEFAULT_BACKPRESSURE_HEALTHY_CHECKS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private configureConnectionLimit(server: Server) {
|
||||||
|
server.maxConnections = this.getMaxConnectionsPerListener();
|
||||||
|
(server as Server & { dropMaxConnection: boolean }).dropMaxConnection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMaxConnectionsPerListener() {
|
||||||
|
return this.getPositiveIntegerEnv('STRATUM_MAX_CONNECTIONS_PER_LISTENER', DEFAULT_MAX_CONNECTIONS_PER_LISTENER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTlsHandshakeTimeoutMs() {
|
||||||
|
return this.getPositiveIntegerEnv('STRATUM_TLS_HANDSHAKE_TIMEOUT_MS', DEFAULT_TLS_HANDSHAKE_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
private getPositiveIntegerEnv(key: string, fallback: number) {
|
private getPositiveIntegerEnv(key: string, fallback: number) {
|
||||||
const configured = parseInt(process.env[key], 10);
|
const configured = parseInt(process.env[key], 10);
|
||||||
if (Number.isFinite(configured) && configured > 0) {
|
if (Number.isFinite(configured) && configured > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user