mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Split maintenance duties from notifier
This commit is contained in:
@@ -60,6 +60,20 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
time: true,
|
time: true,
|
||||||
},
|
},
|
||||||
|
// Non-hot-path master duties: notifications, reporting, and cleanup.
|
||||||
|
{
|
||||||
|
...dockerLogConfig,
|
||||||
|
name: 'maintenance',
|
||||||
|
script: './dist/maintenance-main.js',
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
env: {
|
||||||
|
MASTER: 'true',
|
||||||
|
API_ENABLED: 'false',
|
||||||
|
NODE_CLUSTER_SCHED_POLICY: 'none',
|
||||||
|
},
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
// Worker instances
|
// Worker instances
|
||||||
{
|
{
|
||||||
...dockerLogConfig,
|
...dockerLogConfig,
|
||||||
|
|||||||
@@ -46,6 +46,19 @@ describe('PM2 worker sizing', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('runs maintenance duties outside the isolated notifier process', () => {
|
||||||
|
const config = require('../ecosystem.config.js');
|
||||||
|
|
||||||
|
expect(config.apps.find((app) => app.name === 'maintenance')).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
script: './dist/maintenance-main.js',
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
env: expect.objectContaining({ MASTER: 'true', API_ENABLED: 'false' }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects invalid fixed worker counts instead of silently starting no workers', () => {
|
it('rejects invalid fixed worker counts instead of silently starting no workers', () => {
|
||||||
process.env.STRATUM_WORKERS = 'many';
|
process.env.STRATUM_WORKERS = 'many';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { MaintenanceModule } from './maintenance.module';
|
||||||
|
|
||||||
|
async function bootstrap(): Promise<void> {
|
||||||
|
process.env.MASTER = 'true';
|
||||||
|
process.env.API_ENABLED = 'false';
|
||||||
|
|
||||||
|
const application = await NestFactory.createApplicationContext(
|
||||||
|
MaintenanceModule,
|
||||||
|
);
|
||||||
|
application.enableShutdownHooks();
|
||||||
|
console.log('Maintenance services started');
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootstrap();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { UserAgentReportModule } from './ORM/_views/user-agent-report/user-agent-report.module';
|
||||||
|
import { ClientModule } from './ORM/client/client.module';
|
||||||
|
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
||||||
|
import { ShareAccountingModule } from './ORM/share-accounting/share-accounting.module';
|
||||||
|
import { TelegramSubscriptionsModule } from './ORM/telegram-subscriptions/telegram-subscriptions.module';
|
||||||
|
import { createDatabaseOptions } from './database.config';
|
||||||
|
import { AppService } from './services/app.service';
|
||||||
|
import { DiscordService } from './services/discord.service';
|
||||||
|
import { NotificationService } from './services/notification.service';
|
||||||
|
import { RedisMessagingModule } from './services/redis-messaging.module';
|
||||||
|
import { TelegramService } from './services/telegram.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background process for non-hot-path master duties. Keep this separate from
|
||||||
|
* NotifierModule so chat integrations, reporting, and cleanup timers cannot
|
||||||
|
* delay block-template notification.
|
||||||
|
*/
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot(),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => createDatabaseOptions({
|
||||||
|
...process.env,
|
||||||
|
DB_HOST: configService.get('DB_HOST'),
|
||||||
|
DB_PORT: configService.get('DB_PORT'),
|
||||||
|
DB_USERNAME: configService.get('DB_USERNAME'),
|
||||||
|
DB_PASSWORD: configService.get('DB_PASSWORD'),
|
||||||
|
DB_DATABASE: configService.get('DB_DATABASE'),
|
||||||
|
DB_LOGGING: configService.get('DB_LOGGING'),
|
||||||
|
DB_POOL_SIZE: configService.get('DB_POOL_SIZE'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
RedisMessagingModule,
|
||||||
|
ClientModule,
|
||||||
|
RpcBlocksModule,
|
||||||
|
UserAgentReportModule,
|
||||||
|
ShareAccountingModule,
|
||||||
|
TelegramSubscriptionsModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AppService,
|
||||||
|
DiscordService,
|
||||||
|
NotificationService,
|
||||||
|
TelegramService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class MaintenanceModule { }
|
||||||
@@ -3,12 +3,9 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { createDatabaseOptions } from './database.config';
|
import { createDatabaseOptions } from './database.config';
|
||||||
import { AcceptedShareEntity } from './ORM/accepted-share/accepted-share.entity';
|
|
||||||
import { PayoutSnapshotModule } from './ORM/payout-snapshot/payout-snapshot.module';
|
import { PayoutSnapshotModule } from './ORM/payout-snapshot/payout-snapshot.module';
|
||||||
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
|
||||||
import { ShareAccountingService } from './ORM/share-accounting/share-accounting.service';
|
|
||||||
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
|
||||||
import { PoolSummaryRefreshService } from './services/pool-summary-refresh.service';
|
|
||||||
import { RedisMessagingModule } from './services/redis-messaging.module';
|
import { RedisMessagingModule } from './services/redis-messaging.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,15 +30,12 @@ import { RedisMessagingModule } from './services/redis-messaging.module';
|
|||||||
DB_POOL_SIZE: configService.get('DB_POOL_SIZE'),
|
DB_POOL_SIZE: configService.get('DB_POOL_SIZE'),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
TypeOrmModule.forFeature([AcceptedShareEntity]),
|
|
||||||
RedisMessagingModule,
|
RedisMessagingModule,
|
||||||
RpcBlocksModule,
|
RpcBlocksModule,
|
||||||
PayoutSnapshotModule,
|
PayoutSnapshotModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BitcoinRpcService,
|
BitcoinRpcService,
|
||||||
PoolSummaryRefreshService,
|
|
||||||
ShareAccountingService,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class NotifierModule { }
|
export class NotifierModule { }
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { ShareAccountingService } from '../ORM/share-accounting/share-accounting.service';
|
|
||||||
|
|
||||||
const DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
||||||
const DEFAULT_STARTUP_DELAY_MS = 15 * 1000;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PoolSummaryRefreshService implements OnModuleInit, OnModuleDestroy {
|
|
||||||
private timer: NodeJS.Timeout | null = null;
|
|
||||||
private startupTimer: NodeJS.Timeout | null = null;
|
|
||||||
private refreshing = false;
|
|
||||||
|
|
||||||
constructor(private readonly shareAccountingService: ShareAccountingService) { }
|
|
||||||
|
|
||||||
public onModuleInit(): void {
|
|
||||||
if (process.env.MASTER !== 'true' || process.env.API_ONLY === 'true') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.startupTimer = setTimeout(() => {
|
|
||||||
void this.refresh();
|
|
||||||
}, this.readPositiveInt('POOL_SUMMARY_REFRESH_STARTUP_DELAY_MS', DEFAULT_STARTUP_DELAY_MS));
|
|
||||||
this.startupTimer.unref?.();
|
|
||||||
|
|
||||||
this.timer = setInterval(() => {
|
|
||||||
void this.refresh();
|
|
||||||
}, this.readPositiveInt('POOL_SUMMARY_REFRESH_INTERVAL_MS', DEFAULT_REFRESH_INTERVAL_MS));
|
|
||||||
this.timer.unref?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async onModuleDestroy(): Promise<void> {
|
|
||||||
if (this.startupTimer != null) {
|
|
||||||
clearTimeout(this.startupTimer);
|
|
||||||
this.startupTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.timer != null) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async refresh(): Promise<void> {
|
|
||||||
if (this.refreshing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.refreshing = true;
|
|
||||||
try {
|
|
||||||
await this.shareAccountingService.refreshPoolSummary();
|
|
||||||
await this.shareAccountingService.refreshPoolSummary('pplns');
|
|
||||||
await this.shareAccountingService.refreshPoolSummary('solo');
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed refreshing pool accounting summary: ${error.message}`);
|
|
||||||
} finally {
|
|
||||||
this.refreshing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readPositiveInt(name: string, fallback: number): number {
|
|
||||||
const value = Number(process.env[name]);
|
|
||||||
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user