mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Avoid repeated cold info refreshes
This commit is contained in:
+51
-14
@@ -18,6 +18,7 @@ import { RedisMessagingService } from './services/redis-messaging.service';
|
|||||||
export class AppController {
|
export class AppController {
|
||||||
|
|
||||||
private uptime = new Date();
|
private uptime = new Date();
|
||||||
|
private siteInfoRefreshPromise: Promise<SiteInfoResponse> | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
|
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
|
||||||
@@ -37,27 +38,51 @@ export class AppController {
|
|||||||
|
|
||||||
|
|
||||||
const CACHE_KEY = 'SITE_INFO';
|
const CACHE_KEY = 'SITE_INFO';
|
||||||
|
const STALE_CACHE_KEY = 'SITE_INFO_STALE';
|
||||||
const cachedResult = await this.getCached(CACHE_KEY, 5 * 60 * 1000);
|
const cachedResult = await this.getCached(CACHE_KEY, 5 * 60 * 1000);
|
||||||
|
|
||||||
if (cachedResult != null) {
|
if (cachedResult != null) {
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
let usedFallback = false;
|
const staleResult = await this.getCached<SiteInfoResponse>(STALE_CACHE_KEY, 60 * 60 * 1000);
|
||||||
const withInfoTimeout = async <T>(label: string, promise: Promise<T>, fallback: T): Promise<T> => {
|
if (staleResult != null) {
|
||||||
return await this.withTimeout(label, promise, fallback, () => {
|
void this.refreshSiteInfo(staleResult);
|
||||||
usedFallback = true;
|
return staleResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.refreshSiteInfo(null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refreshSiteInfo(staleInfo: SiteInfoResponse | null): Promise<SiteInfoResponse> {
|
||||||
|
if (this.siteInfoRefreshPromise != null) {
|
||||||
|
return this.siteInfoRefreshPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.siteInfoRefreshPromise = this.loadSiteInfo(staleInfo)
|
||||||
|
.finally(() => {
|
||||||
|
this.siteInfoRefreshPromise = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this.siteInfoRefreshPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadSiteInfo(staleInfo: SiteInfoResponse | null): Promise<SiteInfoResponse> {
|
||||||
|
const CACHE_KEY = 'SITE_INFO';
|
||||||
|
const STALE_CACHE_KEY = 'SITE_INFO_STALE';
|
||||||
|
const withInfoTimeout = async <T>(label: string, promise: Promise<T>, fallback: T): Promise<T> => {
|
||||||
|
return await this.withTimeout(label, promise, fallback);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [blockData, highScores, poolAuthority, userAgentReport] = await Promise.all([
|
const [blockData, highScores, poolAuthority, userAgentReport] = await Promise.all([
|
||||||
withInfoTimeout('found blocks', this.blocksService.getFoundBlocks(), []),
|
withInfoTimeout('found blocks', this.blocksService.getFoundBlocks(), staleInfo?.blockData ?? []),
|
||||||
withInfoTimeout('high scores', this.addressSettingsService.getHighScores(), []),
|
withInfoTimeout('high scores', this.addressSettingsService.getHighScores(), staleInfo?.highScores ?? []),
|
||||||
withInfoTimeout('SV2 authority', this.stratumV2Service.getPoolAuthorityPublicKey(), {
|
withInfoTimeout('SV2 authority', this.stratumV2Service.getPoolAuthorityPublicKey(), {
|
||||||
publicKey: '',
|
publicKey: staleInfo?.sv2?.poolAuthorityPublicKey ?? '',
|
||||||
configured: false
|
configured: staleInfo?.sv2?.authorityKeyConfigured ?? false
|
||||||
}),
|
}),
|
||||||
withInfoTimeout<UserAgentReportView[]>('user agent report', this.userAgentReportService.getReport(), []),
|
withInfoTimeout<UserAgentReportView[]>('user agent report', this.userAgentReportService.getReport(), staleInfo?.userAgents ?? []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const other: {
|
const other: {
|
||||||
@@ -87,7 +112,7 @@ export class AppController {
|
|||||||
userAgents.push({ userAgent: 'Other', count: other.count.toString(), bestDifficulty: other.bestDifficulty, totalHashRate: other.totalHashRate.toString() })
|
userAgents.push({ userAgent: 'Other', count: other.count.toString(), bestDifficulty: other.bestDifficulty, totalHashRate: other.totalHashRate.toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data: SiteInfoResponse = {
|
||||||
blockData,
|
blockData,
|
||||||
userAgents,
|
userAgents,
|
||||||
highScores,
|
highScores,
|
||||||
@@ -98,11 +123,12 @@ export class AppController {
|
|||||||
uptime: this.uptime
|
uptime: this.uptime
|
||||||
};
|
};
|
||||||
|
|
||||||
// Match the pre-Timescale dashboard cache behavior; live accounting is exposed separately.
|
// Cache a complete response even when one slow component falls back to stale data.
|
||||||
await this.setCached(CACHE_KEY, data, usedFallback ? 15 * 1000 : 5 * 60 * 1000);
|
// A short retry loop here causes repeated DB work because timed-out TypeORM queries are not cancelled.
|
||||||
|
await this.setCached(CACHE_KEY, data, 5 * 60 * 1000);
|
||||||
|
await this.setCached(STALE_CACHE_KEY, data, 60 * 60 * 1000);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('info/accounting')
|
@Get('info/accounting')
|
||||||
@@ -211,7 +237,7 @@ export class AppController {
|
|||||||
label: string,
|
label: string,
|
||||||
promise: Promise<T>,
|
promise: Promise<T>,
|
||||||
fallback: T,
|
fallback: T,
|
||||||
onTimeout: () => void,
|
onTimeout: () => void = () => undefined,
|
||||||
timeoutMs = 1500
|
timeoutMs = 1500
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
let timeout: NodeJS.Timeout;
|
let timeout: NodeJS.Timeout;
|
||||||
@@ -246,3 +272,14 @@ export class AppController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SiteInfoResponse {
|
||||||
|
blockData: unknown[];
|
||||||
|
userAgents: UserAgentReportView[];
|
||||||
|
highScores: unknown[];
|
||||||
|
sv2: {
|
||||||
|
poolAuthorityPublicKey: string;
|
||||||
|
authorityKeyConfigured: boolean;
|
||||||
|
};
|
||||||
|
uptime: Date;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user