Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c7930e9d2 | |||
| ec463cb927 |
@@ -43,6 +43,14 @@ export class FeederService {
|
|||||||
private readonly MAX_RETRIES = 50;
|
private readonly MAX_RETRIES = 50;
|
||||||
private readonly DAILY_SYNC_TIME_ZONE = "Europe/Istanbul";
|
private readonly DAILY_SYNC_TIME_ZONE = "Europe/Istanbul";
|
||||||
|
|
||||||
|
/** Watchdog heartbeat – updated on every match/date activity */
|
||||||
|
public lastActivityAt: number = Date.now();
|
||||||
|
|
||||||
|
/** Call this to bump the heartbeat */
|
||||||
|
private heartbeat(): void {
|
||||||
|
this.lastActivityAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly scraperService: FeederScraperService,
|
private readonly scraperService: FeederScraperService,
|
||||||
private readonly transformerService: FeederTransformerService,
|
private readonly transformerService: FeederTransformerService,
|
||||||
@@ -259,6 +267,7 @@ export class FeederService {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { onlyCompletedMatches = false, refreshExistingMatches = false } =
|
const { onlyCompletedMatches = false, refreshExistingMatches = false } =
|
||||||
options;
|
options;
|
||||||
|
this.heartbeat();
|
||||||
this.logger.log(`[${sport}] 📅 Processing: ${dateString}`);
|
this.logger.log(`[${sport}] 📅 Processing: ${dateString}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -431,6 +440,7 @@ export class FeederService {
|
|||||||
refreshExistingMatches,
|
refreshExistingMatches,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.heartbeat();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[${sport}] ✅ successful for ${match.id} ${match.homeTeam.name} vs ${match.awayTeam.name}`,
|
`[${sport}] ✅ successful for ${match.id} ${match.homeTeam.name} vs ${match.awayTeam.name}`,
|
||||||
@@ -443,6 +453,7 @@ export class FeederService {
|
|||||||
failedMatches.push(match);
|
failedMatches.push(match);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
this.heartbeat();
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`[${sport}] Sequential error for ${match.id}: ${e.message}`,
|
`[${sport}] Sequential error for ${match.id}: ${e.message}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,16 @@ import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { createCanvas, loadImage } from "canvas";
|
// Canvas is optional – native module may fail on ARM64 (RPi)
|
||||||
|
let createCanvas: any;
|
||||||
|
let loadImage: any;
|
||||||
|
try {
|
||||||
|
const canvas = require("canvas");
|
||||||
|
createCanvas = canvas.createCanvas;
|
||||||
|
loadImage = canvas.loadImage;
|
||||||
|
} catch {
|
||||||
|
// Canvas unavailable – ImageRendererService methods will throw at runtime if called
|
||||||
|
}
|
||||||
import { PredictionCardDto } from "./dto/prediction-card.dto";
|
import { PredictionCardDto } from "./dto/prediction-card.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* Run Full Historical Feeder
|
* Run Full Historical Feeder
|
||||||
* Usage: npm run feeder:historical
|
* Usage: npm run feeder:historical
|
||||||
|
*
|
||||||
|
* Includes a watchdog that kills the process if no activity
|
||||||
|
* is detected for 5 minutes (stuck API request), letting PM2
|
||||||
|
* auto-restart and resume from DB.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NestFactory } from "@nestjs/core";
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { FeederService } from "../modules/feeder/feeder.service";
|
import { FeederService } from "../modules/feeder/feeder.service";
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
|
|
||||||
|
const WATCHDOG_INTERVAL_MS = 60_000; // Check every 1 minute
|
||||||
|
const WATCHDOG_TIMEOUT_MS = 5 * 60_000; // Kill if no activity for 5 minutes
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
process.env.FEEDER_MODE = "historical";
|
process.env.FEEDER_MODE = "historical";
|
||||||
|
|
||||||
@@ -21,8 +28,25 @@ async function bootstrap() {
|
|||||||
logger: ["log", "error", "warn"],
|
logger: ["log", "error", "warn"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const feederService = app.get(FeederService);
|
||||||
|
|
||||||
|
// ── Watchdog Timer ──────────────────────────────────────────
|
||||||
|
// If the feeder hangs on an API call for 5+ minutes, force-exit
|
||||||
|
// so PM2 can restart and resume from where it left off in DB.
|
||||||
|
const watchdog = setInterval(() => {
|
||||||
|
const idleMs = Date.now() - feederService.lastActivityAt;
|
||||||
|
if (idleMs > WATCHDOG_TIMEOUT_MS) {
|
||||||
|
logger.error(
|
||||||
|
`🐕 WATCHDOG: No activity for ${Math.round(idleMs / 1000)}s. Force-exiting for PM2 restart...`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}, WATCHDOG_INTERVAL_MS);
|
||||||
|
|
||||||
|
// Don't let the watchdog timer keep the process alive after scan finishes
|
||||||
|
watchdog.unref();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const feederService = app.get(FeederService);
|
|
||||||
const startDate = process.env.FEEDER_START_DATE || "2023-06-01";
|
const startDate = process.env.FEEDER_START_DATE || "2023-06-01";
|
||||||
const sports = (process.env.FEEDER_SPORTS || "football,basketball")
|
const sports = (process.env.FEEDER_SPORTS || "football,basketball")
|
||||||
.split(",")
|
.split(",")
|
||||||
@@ -36,6 +60,7 @@ async function bootstrap() {
|
|||||||
logger.error(error.stack);
|
logger.error(error.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} finally {
|
} finally {
|
||||||
|
clearInterval(watchdog);
|
||||||
await app.close();
|
await app.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user