fix: deduplicate concurrent price requests to prevent CoinGecko quota exhaustion#33
Open
luciachizaram wants to merge 1 commit into
Open
Conversation
getFreshPrices() performs a read-check-write on lastRequestTime without any synchronisation. Under concurrent price requests (e.g. multiple BullMQ workers triggering rebalance checks simultaneously), all callers read a stale lastRequestTime, pass the rate-limit guard, and fire simultaneous HTTP requests to CoinGecko — exhausting the free-tier quota in seconds. Add an inflightPriceRequest singleton so that all concurrent callers share one in-flight HTTP request. The promise is cleared in .finally() so subsequent requests after completion proceed normally. Fixes grantFoxin#28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ReflectorService.getFreshPrices()performs a read-check-write onthis.lastRequestTimewithout any synchronisation:JavaScript's single-threaded event loop does not protect against this:
await fetch()yields execution, allowing multiple coroutines to pass the guard before any of them updateslastRequestTime. With ≥4 concurrent BullMQ workers, all callers fire simultaneous HTTP requests to CoinGecko, exhausting the free-tier quota (10–30 req/min) in seconds.After 429s, the service falls back to
getFallbackPrices()which adds ±1% random noise to hardcoded base prices — rebalance thresholds may trigger or suppress incorrectly.Fix
Add an
inflightPriceRequestsingleton promise gate. When a fetch is already in flight, subsequent callers reuse the same promise instead of firing a new request:This collapses all concurrent callers onto one in-flight HTTP request, honouring the rate limit regardless of concurrency level. The
.finally()ensures the gate is cleared whether the fetch succeeds or fails, so subsequent requests after completion proceed normally.Changes
backend/src/services/reflector.ts:inflightPriceRequestpropertygetFreshPrices()now checks for and reuses in-flight requests_doFetchPrices()for clarityFixes #28