diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index a24638e..5af1d1b 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -47,26 +47,8 @@ function getDeps() { const startTime = Date.now() -// Concurrency limiter — gates in-flight scrape work at exactly POOL_SIZE so we -// reject immediately (HTTP 429) instead of letting BrowserPool.acquire() block -// for up to 5s. Prowlarr/Jackett use the 429 to back off cleanly. -let activeJobs = 0 -const MAX_CONCURRENT = POOL_SIZE - -function tryAcquireSlot(): boolean { - if (activeJobs < MAX_CONCURRENT) { - activeJobs++ - return true - } - return false -} - -function releaseSlot(): void { - if (activeJobs > 0) activeJobs-- -} - -// Build a FlareSolverr v2-shaped error envelope. Used by /v1, /scrape, and the -// second-line PoolExhaustedError catch-arm. +// Build a FlareSolverr v2-shaped error envelope. Used by /v1 for every error +// path and by /scrape when the pool is exhausted (PoolExhaustedError → 429). function flareSolverrError(url: string, message: string): FlareSolverrResponse { const now = Date.now() return { @@ -108,8 +90,6 @@ new Elysia() busy: stats.busy, restarts: stats.restarts, queueDepth: 0, - activeJobs, - maxConcurrent: MAX_CONCURRENT, } }) @@ -129,11 +109,6 @@ new Elysia() return flareSolverrError(req.url, "Browser pool initializing, retry in a few seconds") } - if (!tryAcquireSlot()) { - set.status = 429 - return flareSolverrError(req.url, "Browser pool saturated, retry shortly") - } - try { const result = await scrape({ url: req.url, maxTimeout: req.maxTimeout ?? 60_000, headers: req.headers }, getDeps()) return { @@ -154,32 +129,29 @@ new Elysia() } catch (err) { set.status = err instanceof PoolExhaustedError ? 429 : 500 return flareSolverrError(req.url, err instanceof Error ? err.message : String(err)) - } finally { - releaseSlot() } }) - // Native TRAWL API — richer response (tier, timings, sessionCached) + // Native TRAWL API — richer response (tier, timings, sessionCached). + // Error mapping: + // 503 — pool still initializing (native { error }) + // 429 — pool exhausted (FlareSolverr envelope; uniform with /v1) + // 500 — other scrape exception (native { error }) .post("/scrape", async ({ body, set }) => { if (!pool) { set.status = 503 - return flareSolverrError("", "Browser pool initializing, retry in a few seconds") + return { error: "Browser pool initializing, retry in a few seconds" } } - const req = body as ScrapeRequest - - if (!tryAcquireSlot()) { - set.status = 429 - return flareSolverrError(req.url ?? "", "Browser pool saturated, retry shortly") - } - try { return await scrape(req, getDeps()) } catch (err) { - set.status = err instanceof PoolExhaustedError ? 429 : 500 - return flareSolverrError(req.url ?? "", err instanceof Error ? err.message : String(err)) - } finally { - releaseSlot() + if (err instanceof PoolExhaustedError) { + set.status = 429 + return flareSolverrError(req.url ?? "", "Browser pool saturated, retry shortly") + } + set.status = 500 + return { error: err instanceof Error ? err.message : String(err) } } })