Respect Reddit rate limit delays
This commit is contained in:
1 parent
7bcdbe236a
commit
50de1c180d
5 files changed
+77
-10
No files matched your search
+8
-1
@@ -3,6 +3,7 @@ import { withRetry } from './utils/retry.js';
|
||||
import { isSkip } from './services/processor.js';
|
||||
import { reportDeliveryResult, reportInvitationCooldown } from './web/event-reporting.js';
|
||||
import { SessionBoundRuntime } from './services/session-bound-runtime.js';
|
||||
import { RedditRateLimitError } from './reddit/api.js';
|
||||
|
||||
export interface StoppableSchedule {
|
||||
stop(): void;
|
||||
@@ -395,7 +396,13 @@ export async function runMainLoop(app: AppContainer): Promise<void> {
|
||||
}
|
||||
} catch (error) {
|
||||
if (isShuttingDown && error instanceof DOMException && error.name === 'AbortError') break;
|
||||
logger.error('Error in main loop:', error);
|
||||
if (error instanceof RedditRateLimitError) {
|
||||
await sleep(Math.max(ERROR_BACKOFF_MS, error.retryAfterMs + 1_000), shutdownController.signal);
|
||||
continue;
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Error in main loop: ${errorMessage}`);
|
||||
await sleep(ERROR_BACKOFF_MS, shutdownController.signal);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-2
@@ -3,6 +3,26 @@ import type { BrowserContext } from 'playwright';
|
||||
import type { Config } from '../config/index.js';
|
||||
import type { Logger } from '../utils/logger.js';
|
||||
|
||||
export class RedditRateLimitError extends Error {
|
||||
constructor(public readonly retryAfterMs: number) {
|
||||
const retryAfterSeconds = Math.ceil(retryAfterMs / 1000);
|
||||
super(`Reddit API rate limit reached; retry after ${retryAfterSeconds}s`);
|
||||
this.name = 'RedditRateLimitError';
|
||||
}
|
||||
}
|
||||
|
||||
function getRateLimitDelayMs(headers: unknown): number {
|
||||
if (!headers || typeof headers !== 'object') return 60_000;
|
||||
|
||||
const values = headers as Record<string, unknown>;
|
||||
const rawDelay = values['retry-after'] ?? values['x-ratelimit-reset'];
|
||||
const seconds = typeof rawDelay === 'number' ? rawDelay : Number.parseFloat(String(rawDelay));
|
||||
|
||||
return Number.isFinite(seconds) && seconds >= 0
|
||||
? Math.ceil(seconds * 1000)
|
||||
: 60_000;
|
||||
}
|
||||
|
||||
export interface RedditPost {
|
||||
id: string;
|
||||
name: string; // fullname like "t3_xxxxx"
|
||||
@@ -134,8 +154,10 @@ export class RedditApiClient {
|
||||
return [];
|
||||
}
|
||||
if (error.response?.status === 429) {
|
||||
this.logger.warn(`Rate limited by Reddit, waiting...`);
|
||||
throw error;
|
||||
const retryAfterMs = getRateLimitDelayMs(error.response.headers);
|
||||
const rateLimitError = new RedditRateLimitError(retryAfterMs);
|
||||
this.logger.warn(rateLimitError.message);
|
||||
throw rateLimitError;
|
||||
}
|
||||
}
|
||||
this.logger.error(`Error fetching posts from r/${subreddit}:`, error);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RedditApiClient, RedditPost } from '../reddit/api.js';
|
||||
import { RedditRateLimitError, type RedditApiClient, type RedditPost } from '../reddit/api.js';
|
||||
import type { PostsRepository, MessagesRepository } from '../database/repositories.js';
|
||||
import type { Config } from '../config/index.js';
|
||||
import type { Logger } from '../utils/logger.js';
|
||||
@@ -92,7 +92,10 @@ export class MonitorService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to fetch posts from r/${subreddit.name}:`, error);
|
||||
if (error instanceof RedditRateLimitError) throw error;
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(`Failed to fetch posts from r/${subreddit.name}: ${errorMessage}`);
|
||||
}
|
||||
|
||||
// Small delay between subreddit fetches to be nice to Reddit
|
||||
|
||||
Reference in new issue
Block a user