fix: pause Tiny calls globally after rate blocks
This commit is contained in:
@@ -9,6 +9,11 @@ const LOCK_STALE_MS = 30000;
|
|||||||
const DEFAULT_BLOCK_RETRY_DELAY_MS = 120000;
|
const DEFAULT_BLOCK_RETRY_DELAY_MS = 120000;
|
||||||
const DEFAULT_BLOCK_MAX_RETRIES = 30;
|
const DEFAULT_BLOCK_MAX_RETRIES = 30;
|
||||||
|
|
||||||
|
type TinyRateLimitState = {
|
||||||
|
lastRequestAt?: number;
|
||||||
|
blockedUntil?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
const getPositiveNumberEnv = (name: string, fallback: number) => {
|
const getPositiveNumberEnv = (name: string, fallback: number) => {
|
||||||
@@ -22,17 +27,20 @@ const getBlockMaxRetries = () => getPositiveNumberEnv('TINY_BLOCK_MAX_RETRIES',
|
|||||||
const getLockDir = () => process.env.TINY_RATE_LIMIT_LOCK_DIR || DEFAULT_LOCK_DIR;
|
const getLockDir = () => process.env.TINY_RATE_LIMIT_LOCK_DIR || DEFAULT_LOCK_DIR;
|
||||||
const getStateFile = () => process.env.TINY_RATE_LIMIT_STATE_FILE || DEFAULT_STATE_FILE;
|
const getStateFile = () => process.env.TINY_RATE_LIMIT_STATE_FILE || DEFAULT_STATE_FILE;
|
||||||
|
|
||||||
const readLastRequestAt = () => {
|
const readState = (): TinyRateLimitState => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(fs.readFileSync(getStateFile(), 'utf-8'));
|
const parsed = JSON.parse(fs.readFileSync(getStateFile(), 'utf-8'));
|
||||||
return Number(parsed.lastRequestAt || 0);
|
return {
|
||||||
|
lastRequestAt: Number(parsed.lastRequestAt || 0),
|
||||||
|
blockedUntil: Number(parsed.blockedUntil || 0)
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return 0;
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeLastRequestAt = (timestamp: number) => {
|
const writeState = (state: TinyRateLimitState) => {
|
||||||
fs.writeFileSync(getStateFile(), JSON.stringify({ lastRequestAt: timestamp }, null, 2));
|
fs.writeFileSync(getStateFile(), JSON.stringify(state, null, 2));
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeStaleLock = (lockDir: string) => {
|
const removeStaleLock = (lockDir: string) => {
|
||||||
@@ -67,21 +75,53 @@ const releaseLock = (lockDir: string) => {
|
|||||||
|
|
||||||
const waitForTinySlot = async () => {
|
const waitForTinySlot = async () => {
|
||||||
const minIntervalMs = getMinIntervalMs();
|
const minIntervalMs = getMinIntervalMs();
|
||||||
if (!minIntervalMs) return;
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const lockDir = await acquireLock();
|
||||||
|
let waitMs = 0;
|
||||||
|
let reason = 'next Tiny API request';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = readState();
|
||||||
|
const now = Date.now();
|
||||||
|
const lastRequestAt = Number(state.lastRequestAt || 0);
|
||||||
|
const blockedUntil = Number(state.blockedUntil || 0);
|
||||||
|
const intervalWaitMs = minIntervalMs ? Math.max(0, minIntervalMs - (now - lastRequestAt)) : 0;
|
||||||
|
const blockWaitMs = Math.max(0, blockedUntil - now);
|
||||||
|
|
||||||
|
if (blockWaitMs > intervalWaitMs) {
|
||||||
|
waitMs = blockWaitMs;
|
||||||
|
reason = 'Tiny API block cooldown';
|
||||||
|
} else {
|
||||||
|
waitMs = intervalWaitMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!waitMs) {
|
||||||
|
writeState({
|
||||||
|
...state,
|
||||||
|
lastRequestAt: now,
|
||||||
|
blockedUntil: blockWaitMs ? blockedUntil : 0
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
releaseLock(lockDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Tiny Rate Limit] Waiting ${waitMs}ms before ${reason}.`);
|
||||||
|
await sleep(waitMs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pauseAllTinyRequests = async (delayMs: number) => {
|
||||||
const lockDir = await acquireLock();
|
const lockDir = await acquireLock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lastRequestAt = readLastRequestAt();
|
const state = readState();
|
||||||
const elapsed = Date.now() - lastRequestAt;
|
writeState({
|
||||||
const waitMs = Math.max(0, minIntervalMs - elapsed);
|
...state,
|
||||||
|
blockedUntil: Math.max(Number(state.blockedUntil || 0), Date.now() + delayMs)
|
||||||
if (waitMs > 0) {
|
});
|
||||||
console.log(`[Tiny Rate Limit] Waiting ${waitMs}ms before next Tiny API request.`);
|
|
||||||
await sleep(waitMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLastRequestAt(Date.now());
|
|
||||||
} finally {
|
} finally {
|
||||||
releaseLock(lockDir);
|
releaseLock(lockDir);
|
||||||
}
|
}
|
||||||
@@ -121,7 +161,8 @@ export const tinyPost = async (servicePhp: string, params: URLSearchParams) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const delayMs = getBlockRetryDelayMs();
|
const delayMs = getBlockRetryDelayMs();
|
||||||
console.warn(`[Tiny Rate Limit] Tiny blocked ${servicePhp}. Waiting ${delayMs}ms before retry ${attempt}/${maxRetries}.`);
|
await pauseAllTinyRequests(delayMs);
|
||||||
|
console.warn(`[Tiny Rate Limit] Tiny blocked ${servicePhp}. Pausing all Tiny API requests for ${delayMs}ms before retry ${attempt}/${maxRetries}.`);
|
||||||
await sleep(delayMs);
|
await sleep(delayMs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user