perf(api): short per-call timeout + more retries for PartsCatalogs VIN decode
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
The DataImpulse proxy has dead ports that stall ~10s on connect, and undici's ProxyAgent connect.timeout does NOT bound the proxy connection (verified: runtime still 10s despite the Phase-2 setting). With the 30s request timeout, 2-3 dead ports blow the 25s decode budget → false misses + 12-32s p95 (prod). /car/info answers in <1s on a healthy proxy, so bound it at the fetch level instead: a 6s per-call AbortSignal + 4 retries (both env-tunable: PCAT_DECODE_TIMEOUT_MS / PCAT_DECODE_MAX_RETRIES) make a stuck port abort fast and rotate to a live one within budget. Helps every PCAT decode, not just Renault. Band-aid for proxy flakiness; proper fix is proxy port health-tracking (follow-up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,13 @@ const MAX_RETRIES = Number(process.env.PCAT_MAX_RETRIES) || 2;
|
||||
// timeout before the retry rotates to a fresh port — three of those blow the
|
||||
// caller's 25s decode budget. Fail fast so retries reach a live port in time.
|
||||
const PROXY_CONNECT_TIMEOUT = Number(process.env.PCAT_PROXY_CONNECT_TIMEOUT_MS) || 6_000;
|
||||
// VIN decode (/car/info) answers in <1s on a healthy proxy, so a long timeout only
|
||||
// prolongs dead-port connects. undici's ProxyAgent connect.timeout does NOT bound the
|
||||
// connection to the proxy itself (it stays at undici's 10s default), so we bound it at
|
||||
// the fetch level: a short per-call timeout + extra retries makes a stuck DataImpulse
|
||||
// port abort fast and rotate to a live one within the caller's 25s decode budget.
|
||||
const DECODE_REQUEST_TIMEOUT = Number(process.env.PCAT_DECODE_TIMEOUT_MS) || 6_000;
|
||||
const DECODE_MAX_RETRIES = Number(process.env.PCAT_DECODE_MAX_RETRIES) || 4;
|
||||
|
||||
@Injectable()
|
||||
export class PartsCatalogsService {
|
||||
@@ -52,7 +59,10 @@ export class PartsCatalogsService {
|
||||
outcome?: { transient: boolean },
|
||||
): Promise<PcatVinResult | null> {
|
||||
try {
|
||||
const data = await this.fetchWithAuth("/car/info", { q: vin }, signal);
|
||||
const data = await this.fetchWithAuth("/car/info", { q: vin }, signal, {
|
||||
timeoutMs: DECODE_REQUEST_TIMEOUT,
|
||||
maxRetries: DECODE_MAX_RETRIES,
|
||||
});
|
||||
|
||||
if (!data || typeof data !== "object") {
|
||||
return null;
|
||||
@@ -192,8 +202,10 @@ export class PartsCatalogsService {
|
||||
endpoint: string,
|
||||
params?: Record<string, string>,
|
||||
externalSignal?: AbortSignal,
|
||||
opts?: { timeoutMs?: number; maxRetries?: number },
|
||||
): Promise<any> {
|
||||
const maxRetries = MAX_RETRIES;
|
||||
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
||||
const timeoutMs = opts?.timeoutMs ?? REQUEST_TIMEOUT;
|
||||
|
||||
let session: PcatSession | null = null;
|
||||
|
||||
@@ -211,7 +223,7 @@ export class PartsCatalogsService {
|
||||
}
|
||||
|
||||
try {
|
||||
const signals = [AbortSignal.timeout(REQUEST_TIMEOUT)];
|
||||
const signals = [AbortSignal.timeout(timeoutMs)];
|
||||
if (externalSignal) signals.push(externalSignal);
|
||||
const fetchOptions: RequestInit & { dispatcher?: any } = {
|
||||
method: "GET",
|
||||
|
||||
Reference in New Issue
Block a user