-
Notifications
You must be signed in to change notification settings - Fork 0
fix: harden crawl filename handling and stale recovery #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,20 @@ DATABASE_URL=postgresql://dxd:dxd@localhost:5432/scraper | |||||||
| # Redis | ||||||||
| REDIS_URL=redis://localhost:6379 | ||||||||
|
|
||||||||
| # Worker recovery | ||||||||
| # BullMQ lock duration. Defaults to 10 minutes. | ||||||||
| # WORKER_LOCK_DURATION_MS=600000 | ||||||||
| # Grace period before orphaned active crawls are considered stale. Defaults to 10 minutes. | ||||||||
| # ORPHAN_CRAWL_GRACE_MS=600000 | ||||||||
| # Reconcile interval for orphaned active crawls (in milliseconds). Defaults to 2 minutes. | ||||||||
| # This determines how frequently the system checks for orphaned crawls. | ||||||||
| # Should be less than ORPHAN_CRAWL_GRACE_MS to detect stale crawls before they expire. | ||||||||
| # Works in conjunction with WORKER_LOCK_DURATION_MS to manage worker recovery timing. | ||||||||
| # ORPHAN_CRAWL_RECONCILE_INTERVAL_MS=120000 | ||||||||
| # Temporary escape hatch to restore the old skip-lock-renewal behavior. | ||||||||
| # Accepts true/1/yes/on to enable. | ||||||||
| # WORKER_SKIP_LOCK_RENEWAL=false | ||||||||
|
|
||||||||
| # Storage | ||||||||
| STORAGE_TYPE=auto | ||||||||
| LOCAL_STORAGE_PATH=./data | ||||||||
|
|
@@ -29,4 +43,4 @@ FRONTEND_URL=http://localhost:5173 | |||||||
| # CORS_ALLOWED_ORIGINS= | ||||||||
|
|
||||||||
| # Web | ||||||||
| VITE_API_URL=http://localhost:3001 | ||||||||
| VITE_API_URL=http://localhost:3001 | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add blank line at end of file. The dotenv-linter tool flags the missing trailing newline. Adding a blank line at the end follows standard convention and prevents potential issues with certain tools. 📝 Proposed fix VITE_API_URL=http://localhost:3001
+📝 Committable suggestion
Suggested change
🧰 Tools🪛 dotenv-linter (4.0.0)[warning] 46-46: [EndingBlankLine] No blank line at the end of the file (EndingBlankLine) 🤖 Prompt for AI Agents |
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import assert from "node:assert/strict"; | ||
| import { describe, it } from "node:test"; | ||
| import { getWorkerRuntimeConfig } from "./worker-config.js"; | ||
|
|
||
| describe("getWorkerRuntimeConfig", () => { | ||
| it("uses 10 minute stale-recovery defaults and keeps lock renewal enabled", () => { | ||
| const config = getWorkerRuntimeConfig({}); | ||
|
|
||
| assert.equal(config.crawlConcurrency, 1); | ||
| assert.equal(config.archiveConcurrency, 1); | ||
| assert.equal(config.lockDuration, 10 * 60 * 1000); | ||
| assert.equal(config.stalledInterval, 120000); | ||
| assert.equal(config.orphanGraceMs, 10 * 60 * 1000); | ||
| assert.equal(config.reconcileIntervalMs, 120000); | ||
| assert.equal(config.skipLockRenewal, false); | ||
| }); | ||
|
|
||
| it("respects positive environment overrides", () => { | ||
| const config = getWorkerRuntimeConfig({ | ||
| WORKER_CRAWL_CONCURRENCY: "2", | ||
| WORKER_ARCHIVE_CONCURRENCY: "3", | ||
| WORKER_LOCK_DURATION_MS: "720000", | ||
| WORKER_STALLED_INTERVAL_MS: "60000", | ||
| ORPHAN_CRAWL_GRACE_MS: "480000", | ||
| ORPHAN_CRAWL_RECONCILE_INTERVAL_MS: "30000", | ||
| WORKER_SKIP_LOCK_RENEWAL: "true", | ||
| } as NodeJS.ProcessEnv); | ||
|
|
||
| assert.equal(config.crawlConcurrency, 2); | ||
| assert.equal(config.archiveConcurrency, 3); | ||
| assert.equal(config.lockDuration, 720000); | ||
| assert.equal(config.stalledInterval, 60000); | ||
| assert.equal(config.orphanGraceMs, 480000); | ||
| assert.equal(config.reconcileIntervalMs, 30000); | ||
| assert.equal(config.skipLockRenewal, true); | ||
| }); | ||
|
|
||
| it("accepts common truthy values for skipLockRenewal", () => { | ||
| for (const raw of ["true", "TRUE", "1", "yes", "on"]) { | ||
| const config = getWorkerRuntimeConfig({ | ||
| WORKER_SKIP_LOCK_RENEWAL: raw, | ||
| } as NodeJS.ProcessEnv); | ||
|
|
||
| assert.equal(config.skipLockRenewal, true, `expected ${raw} to enable skipLockRenewal`); | ||
| } | ||
| }); | ||
|
|
||
| it("keeps skipLockRenewal disabled for falsey or absent values", () => { | ||
| for (const raw of [undefined, "false", "0", "no", "off", "unexpected"]) { | ||
| const config = getWorkerRuntimeConfig( | ||
| raw === undefined | ||
| ? {} | ||
| : ({ | ||
| WORKER_SKIP_LOCK_RENEWAL: raw, | ||
| } as NodeJS.ProcessEnv) | ||
| ); | ||
|
|
||
| assert.equal(config.skipLockRenewal, false, `expected ${String(raw)} to keep skipLockRenewal disabled`); | ||
| } | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| export type WorkerRuntimeConfig = { | ||
| crawlConcurrency: number; | ||
| archiveConcurrency: number; | ||
| lockDuration: number; | ||
| stalledInterval: number; | ||
| orphanGraceMs: number; | ||
| reconcileIntervalMs: number; | ||
| skipLockRenewal: boolean; | ||
| }; | ||
|
|
||
| function readPositiveIntEnv(env: NodeJS.ProcessEnv, name: string, fallback: number): number { | ||
| const raw = env[name]; | ||
| if (!raw) { | ||
| return fallback; | ||
| } | ||
|
|
||
| const parsed = Number.parseInt(raw, 10); | ||
| if (!Number.isFinite(parsed) || parsed <= 0) { | ||
| return fallback; | ||
| } | ||
|
|
||
| return parsed; | ||
| } | ||
|
|
||
| function readTruthyEnv(env: NodeJS.ProcessEnv, name: string): boolean { | ||
| const raw = env[name]; | ||
| if (!raw) { | ||
| return false; | ||
| } | ||
|
|
||
| switch (raw.trim().toLowerCase()) { | ||
| case "true": | ||
| case "1": | ||
| case "yes": | ||
| case "on": | ||
| return true; | ||
| default: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| export function getWorkerRuntimeConfig(env: NodeJS.ProcessEnv = process.env): WorkerRuntimeConfig { | ||
| return { | ||
| crawlConcurrency: readPositiveIntEnv(env, "WORKER_CRAWL_CONCURRENCY", 1), | ||
| archiveConcurrency: readPositiveIntEnv(env, "WORKER_ARCHIVE_CONCURRENCY", 1), | ||
| lockDuration: readPositiveIntEnv(env, "WORKER_LOCK_DURATION_MS", 10 * 60 * 1000), | ||
| stalledInterval: readPositiveIntEnv(env, "WORKER_STALLED_INTERVAL_MS", 120000), | ||
| orphanGraceMs: readPositiveIntEnv(env, "ORPHAN_CRAWL_GRACE_MS", 10 * 60 * 1000), | ||
| reconcileIntervalMs: readPositiveIntEnv(env, "ORPHAN_CRAWL_RECONCILE_INTERVAL_MS", 120000), | ||
| skipLockRenewal: readTruthyEnv(env, "WORKER_SKIP_LOCK_RENEWAL"), | ||
| }; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.