mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Migrate postgresql improvements to sqlite
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Server, Socket } from 'net';
|
||||
import { monitorEventLoopDelay } from 'perf_hooks';
|
||||
|
||||
import { StratumV1Client } from '../models/StratumV1Client';
|
||||
import { AddressSettingsService } from '../ORM/address-settings/address-settings.service';
|
||||
@@ -8,84 +9,269 @@ import { BlocksService } from '../ORM/blocks/blocks.service';
|
||||
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
||||
import { ClientService } from '../ORM/client/client.service';
|
||||
import { BitcoinRpcService } from './bitcoin-rpc.service';
|
||||
import { ExternalSharesService } from './external-shares.service';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { StratumV1JobsService } from './stratum-v1-jobs.service';
|
||||
import { ExternalSharesService } from './external-shares.service';
|
||||
|
||||
interface StratumListenerState {
|
||||
port: number;
|
||||
server: Server | null;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_BACKPRESSURE_CHECK_INTERVAL_MS = 5000;
|
||||
const DEFAULT_BACKPRESSURE_EVENT_LOOP_P95_MS = 2000;
|
||||
const DEFAULT_BACKPRESSURE_EVENT_LOOP_RESUME_P95_MS = 250;
|
||||
const DEFAULT_BACKPRESSURE_RSS_MB = 2500;
|
||||
const DEFAULT_BACKPRESSURE_RESUME_RSS_MB = 2000;
|
||||
const DEFAULT_BACKPRESSURE_HEALTHY_CHECKS = 3;
|
||||
const DEFAULT_MAX_CONNECTIONS_PER_LISTENER = 10000;
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class StratumV1Service implements OnModuleInit {
|
||||
|
||||
constructor(
|
||||
private readonly bitcoinRpcService: BitcoinRpcService,
|
||||
private readonly clientService: ClientService,
|
||||
private readonly clientStatisticsService: ClientStatisticsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly blocksService: BlocksService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly stratumV1JobsService: StratumV1JobsService,
|
||||
private readonly addressSettingsService: AddressSettingsService,
|
||||
private readonly externalSharesService: ExternalSharesService
|
||||
) {
|
||||
private readonly listeners: StratumListenerState[] = [];
|
||||
private readonly eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
|
||||
private backpressureMonitor: NodeJS.Timeout | null = null;
|
||||
private healthyBackpressureChecks = 0;
|
||||
|
||||
}
|
||||
constructor(
|
||||
private readonly bitcoinRpcService: BitcoinRpcService,
|
||||
private readonly clientService: ClientService,
|
||||
private readonly clientStatisticsService: ClientStatisticsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly blocksService: BlocksService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly stratumV1JobsService: StratumV1JobsService,
|
||||
private readonly addressSettingsService: AddressSettingsService,
|
||||
private readonly externalSharesService: ExternalSharesService
|
||||
) {
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
}
|
||||
|
||||
if (process.env.NODE_APP_INSTANCE == '0') {
|
||||
await this.clientService.deleteAll();
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.startSocketServer();
|
||||
}, 1000 * 10)
|
||||
async onModuleInit(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
private startSocketServer() {
|
||||
const server = new Server(async (socket: Socket) => {
|
||||
|
||||
//5 min
|
||||
socket.setTimeout(1000 * 60 * 5);
|
||||
|
||||
const client = new StratumV1Client(
|
||||
socket,
|
||||
this.stratumV1JobsService,
|
||||
this.bitcoinRpcService,
|
||||
this.clientService,
|
||||
this.clientStatisticsService,
|
||||
this.notificationService,
|
||||
this.blocksService,
|
||||
this.configService,
|
||||
this.addressSettingsService,
|
||||
this.externalSharesService
|
||||
);
|
||||
|
||||
|
||||
socket.on('close', async (hadError: boolean) => {
|
||||
if (client.extraNonceAndSessionId != null) {
|
||||
// Handle socket disconnection
|
||||
await client.destroy();
|
||||
console.log(`Client ${client.extraNonceAndSessionId} disconnected, hadError?:${hadError}`);
|
||||
if (process.env.NODE_APP_INSTANCE == '0' || process.env.MASTER == 'true') {
|
||||
await this.clientService.deleteAll();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
console.log('socket timeout');
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
});
|
||||
if (process.env.MASTER == 'true') {
|
||||
console.log('Master process skipping Stratum socket listeners');
|
||||
return;
|
||||
}
|
||||
|
||||
socket.on('error', async (error: Error) => { });
|
||||
setTimeout(() => {
|
||||
this.startSocketServer(this.getStratumPort());
|
||||
}, 1000 * 10);
|
||||
|
||||
// //console.log(`Client disconnected, socket error, ${client.extraNonceAndSessionId}`);
|
||||
this.startBackpressureMonitor();
|
||||
|
||||
}
|
||||
|
||||
private startSocketServer(port: number) {
|
||||
const listener: StratumListenerState = {
|
||||
port,
|
||||
server: null,
|
||||
paused: false
|
||||
};
|
||||
this.listeners.push(listener);
|
||||
this.listen(listener);
|
||||
}
|
||||
|
||||
private createSocketServer(): Server {
|
||||
const server = new Server(async (socket: Socket) => {
|
||||
|
||||
//5 min
|
||||
socket.setTimeout(1000 * 60 * 5);
|
||||
|
||||
const client = new StratumV1Client(
|
||||
socket,
|
||||
this.stratumV1JobsService,
|
||||
this.bitcoinRpcService,
|
||||
this.clientService,
|
||||
this.clientStatisticsService,
|
||||
this.notificationService,
|
||||
this.blocksService,
|
||||
this.configService,
|
||||
this.addressSettingsService,
|
||||
this.externalSharesService
|
||||
);
|
||||
|
||||
|
||||
});
|
||||
socket.on('close', async (hadError: boolean) => {
|
||||
if (client.extraNonceAndSessionId != null) {
|
||||
// Handle socket disconnection
|
||||
await client.destroy();
|
||||
console.log(`Client ${client.extraNonceAndSessionId} disconnected, hadError?:${hadError}`);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(process.env.STRATUM_PORT, () => {
|
||||
console.log(`Stratum server is listening on port ${process.env.STRATUM_PORT}`);
|
||||
});
|
||||
socket.on('timeout', () => {
|
||||
console.log('socket timeout');
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
}
|
||||
socket.on('error', async (error: Error) => { });
|
||||
|
||||
}
|
||||
// //console.log(`Client disconnected, socket error, ${client.extraNonceAndSessionId}`);
|
||||
|
||||
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error(`Server error: ${err.message}`);
|
||||
});
|
||||
this.configureConnectionLimit(server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
private listen(listener: StratumListenerState) {
|
||||
if (listener.server != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const server = this.createSocketServer();
|
||||
listener.server = server;
|
||||
listener.paused = false;
|
||||
|
||||
server.listen(listener.port, () => {
|
||||
console.log(`Stratum server is listening on port ${listener.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
private startBackpressureMonitor() {
|
||||
if (this.isBackpressureDisabled() || this.backpressureMonitor != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventLoopDelay.enable();
|
||||
this.backpressureMonitor = setInterval(() => {
|
||||
this.checkBackpressure();
|
||||
}, this.getBackpressureCheckIntervalMs());
|
||||
}
|
||||
|
||||
private checkBackpressure() {
|
||||
const eventLoopP95Ms = this.getEventLoopP95Ms();
|
||||
const rssMb = Math.round(process.memoryUsage().rss / 1024 / 1024);
|
||||
const overloaded = eventLoopP95Ms >= this.getBackpressureEventLoopP95Ms()
|
||||
|| rssMb >= this.getBackpressureRssMb();
|
||||
const paused = this.listeners.some(listener => listener.paused);
|
||||
|
||||
if (overloaded) {
|
||||
this.healthyBackpressureChecks = 0;
|
||||
if (!paused) {
|
||||
this.pauseAccepting(eventLoopP95Ms, rssMb);
|
||||
}
|
||||
this.eventLoopDelay.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
this.eventLoopDelay.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const healthy = eventLoopP95Ms <= this.getBackpressureResumeEventLoopP95Ms()
|
||||
&& rssMb <= this.getBackpressureResumeRssMb();
|
||||
if (!healthy) {
|
||||
this.healthyBackpressureChecks = 0;
|
||||
this.eventLoopDelay.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
this.healthyBackpressureChecks++;
|
||||
if (this.healthyBackpressureChecks >= this.getBackpressureHealthyChecks()) {
|
||||
this.resumeAccepting(eventLoopP95Ms, rssMb);
|
||||
this.healthyBackpressureChecks = 0;
|
||||
}
|
||||
|
||||
this.eventLoopDelay.reset();
|
||||
}
|
||||
|
||||
private pauseAccepting(eventLoopP95Ms: number, rssMb: number) {
|
||||
console.warn(`Pausing Stratum accepts: eventLoopP95Ms=${eventLoopP95Ms}, rssMb=${rssMb}`);
|
||||
for (const listener of this.listeners) {
|
||||
if (listener.paused || listener.server == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const server = listener.server;
|
||||
listener.server = null;
|
||||
listener.paused = true;
|
||||
server.close((error) => {
|
||||
if (error != null) {
|
||||
console.error(`Error while pausing Stratum listener on port ${listener.port}: ${error.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private resumeAccepting(eventLoopP95Ms: number, rssMb: number) {
|
||||
console.warn(`Resuming Stratum accepts: eventLoopP95Ms=${eventLoopP95Ms}, rssMb=${rssMb}`);
|
||||
for (const listener of this.listeners) {
|
||||
if (!listener.paused || listener.server != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.listen(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private configureConnectionLimit(server: Server) {
|
||||
server.maxConnections = this.getMaxConnectionsPerListener();
|
||||
(server as Server & { dropMaxConnection: boolean }).dropMaxConnection = true;
|
||||
}
|
||||
|
||||
private getEventLoopP95Ms() {
|
||||
return Math.round(this.eventLoopDelay.percentile(95) / 1e6);
|
||||
}
|
||||
|
||||
private isBackpressureDisabled() {
|
||||
return process.env.STRATUM_BACKPRESSURE_ENABLED?.toLowerCase() === 'false';
|
||||
}
|
||||
|
||||
private getBackpressureCheckIntervalMs() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_CHECK_INTERVAL_MS', DEFAULT_BACKPRESSURE_CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private getBackpressureEventLoopP95Ms() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_EVENT_LOOP_P95_MS', DEFAULT_BACKPRESSURE_EVENT_LOOP_P95_MS);
|
||||
}
|
||||
|
||||
private getBackpressureResumeEventLoopP95Ms() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_EVENT_LOOP_RESUME_P95_MS', DEFAULT_BACKPRESSURE_EVENT_LOOP_RESUME_P95_MS);
|
||||
}
|
||||
|
||||
private getBackpressureRssMb() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_RSS_MB', DEFAULT_BACKPRESSURE_RSS_MB);
|
||||
}
|
||||
|
||||
private getBackpressureResumeRssMb() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_RESUME_RSS_MB', DEFAULT_BACKPRESSURE_RESUME_RSS_MB);
|
||||
}
|
||||
|
||||
private getBackpressureHealthyChecks() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_BACKPRESSURE_HEALTHY_CHECKS', DEFAULT_BACKPRESSURE_HEALTHY_CHECKS);
|
||||
}
|
||||
|
||||
private getMaxConnectionsPerListener() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_MAX_CONNECTIONS_PER_LISTENER', DEFAULT_MAX_CONNECTIONS_PER_LISTENER);
|
||||
}
|
||||
|
||||
private getStratumPort() {
|
||||
return this.getPositiveIntegerEnv('STRATUM_PORT', 3333);
|
||||
}
|
||||
|
||||
private getPositiveIntegerEnv(key: string, fallback: number) {
|
||||
const configured = parseInt(process.env[key], 10);
|
||||
if (Number.isFinite(configured) && configured > 0) {
|
||||
return configured;
|
||||
}
|
||||
return fallback;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user