90 lines
3.9 KiB
Markdown
90 lines
3.9 KiB
Markdown
# Execution Plan: Fix Scheduler Concurrency and Browser Stability
|
|
|
|
## Context
|
|
The application is experiencing two related issues with the Instagram authentication scheduler:
|
|
1. **Console Spam**: "Auth renewal already in progress" is logged repeatedly. This indicates the scheduler is triggering new renewal attempts while a previous one is still active (or perceived as active). This is likely caused by an invalid or extremely short interval configuration (e.g., `NaN` resulting from parsing failure).
|
|
2. **Browser Instability**: "Target page, context or browser has been closed" errors. This occurs when the scheduler attempts to use a cached Playwright browser instance that has crashed or disconnected.
|
|
|
|
## Goal
|
|
Ensure the authentication scheduler runs reliably at the configured interval without overlapping executions, and make the browser instance management robust against crashes.
|
|
|
|
## Exception to workflow
|
|
Do not create a dedicated branch. It's a fix on a new feature.
|
|
|
|
## Proposed Solution
|
|
|
|
### Story 1: Fix Scheduler Configuration and Resource Cleanup
|
|
**Objective**: Prevent rapid-fire execution of the scheduler and ensure browser resources are cleaned up properly even when errors occur.
|
|
|
|
**Changes**:
|
|
1. **Validate Configuration**: In `src/lib/server/scheduler.ts`, update `getConfig()` to strictly validate `intervalMinutes`.
|
|
* Handle `NaN` (parsing errors).
|
|
* Enforce a minimum interval (e.g., 15 minutes) to prevent spamming.
|
|
* Default to 720 minutes if invalid.
|
|
2. **Improve Resource Management**: Refactor `renewInstagramAuth` to ensure `page` and `context` are closed in a `finally` block (or nested `try/finally`), preventing resource leaks if an error occurs during the renewal process.
|
|
|
|
**Verification**:
|
|
* Set `AUTH_SCHEDULER_INTERVAL_MINUTES` to an invalid value (e.g., "abc") and verify it defaults to 720.
|
|
* Verify that `setInterval` is called with a valid duration.
|
|
|
|
### Story 2: Robust Browser Lifecycle Management
|
|
**Objective**: Ensure the application automatically recovers from browser crashes by detecting disconnected instances.
|
|
|
|
**Changes**:
|
|
1. **Check Connection Status**: In `src/lib/server/browser.ts`, update `getBrowser()` to check `browser.isConnected()`.
|
|
2. **Auto-Recovery**: If the cached browser instance is not connected:
|
|
* Log a warning.
|
|
* Attempt to close the dead instance (swallowing errors).
|
|
* Re-initialize a new browser instance.
|
|
|
|
**Verification**:
|
|
* Simulate a browser crash (e.g., by manually killing the chrome process if possible, or mocking `isConnected` to return false).
|
|
* Verify that the next call to `getBrowser()` creates a new instance instead of throwing.
|
|
|
|
## Implementation Details
|
|
|
|
### `src/lib/server/scheduler.ts`
|
|
```typescript
|
|
function getConfig(): SchedulerConfig {
|
|
const enabled = env.AUTH_SCHEDULER_ENABLED === 'true';
|
|
let intervalMinutes = parseInt(env.AUTH_SCHEDULER_INTERVAL_MINUTES || '720', 10);
|
|
|
|
if (isNaN(intervalMinutes) || intervalMinutes < 15) {
|
|
console.warn(`[Scheduler] Invalid or too short interval '${env.AUTH_SCHEDULER_INTERVAL_MINUTES}'. Defaulting to 720 minutes.`);
|
|
intervalMinutes = 720;
|
|
}
|
|
|
|
return { enabled, intervalMinutes };
|
|
}
|
|
|
|
// In renewInstagramAuth:
|
|
let context = null;
|
|
let page = null;
|
|
try {
|
|
// ... setup ...
|
|
context = await browser.newContext(...);
|
|
page = await context.newPage();
|
|
// ... logic ...
|
|
} catch (e) {
|
|
// ... error handling ...
|
|
} finally {
|
|
if (page) await page.close().catch(() => {});
|
|
if (context) await context.close().catch(() => {});
|
|
state.isRenewing = false;
|
|
}
|
|
```
|
|
|
|
### `src/lib/server/browser.ts`
|
|
```typescript
|
|
export async function getBrowser(): Promise<Browser> {
|
|
if (!browser || !browser.isConnected()) {
|
|
if (browser) {
|
|
console.warn('Browser is disconnected. Re-initializing...');
|
|
try { await browser.close(); } catch (e) { /* ignore */ }
|
|
}
|
|
return initializeBrowser();
|
|
}
|
|
return browser;
|
|
}
|
|
```
|