Location
core/src/exchanges/polymarket/websocket.ts:442-446
Code
private handleTrade(event: any) {
// ...
const resolvers = this.tradeResolvers.get(id);
if (resolvers && resolvers.length > 0) {
resolvers.forEach((r) => r.resolve([trade]));
this.tradeResolvers.set(id, []);
} else {
if (!this.pendingTrades.has(id)) {
this.pendingTrades.set(id, []);
}
this.pendingTrades.get(id)!.push(trade); // ← unbounded
}
}
Growth Pattern
handleTrade is called on every last_trade_price WebSocket message from Polymarket. When no watchTrades() caller is awaiting (i.e., tradeResolvers for that asset is empty), the trade is buffered in pendingTrades. This buffer is only drained when the next watchTrades() call comes in (line 147–149). If a caller subscribes to market data via subscribe() but then stops calling watchTrades() (e.g., their streaming loop is paused, or they only called watchOrderBook), the per-asset trade buffer grows without bound. There is no max size, no TTL, and no eviction.
OOM Estimate
- Each
Trade object: ~200 bytes
- Polymarket active markets: ~100–500 assets subscribed simultaneously is realistic
- Trade frequency: 5–50 events/sec per active market during peak hours
- Conservative: 50 assets × 5 trades/sec = 250 trades/sec buffered when no watcher is active
- After 30 minutes: 450,000 Trade objects ≈ 90 MB
- After 2 hours at peak (50 TPS × 200 assets): ~720 MB → OOM on default Node heap
Suggested Fix
Implement a ring buffer per asset: cap pendingTrades.get(id) at e.g. 1000 entries; when the cap is reached, drop the oldest trade (shift()). Alternatively, expose a maxPendingTrades config option on PolymarketWebSocketConfig.
Found by automated unbounded operations audit
Location
core/src/exchanges/polymarket/websocket.ts:442-446Code
Growth Pattern
handleTradeis called on everylast_trade_priceWebSocket message from Polymarket. When nowatchTrades()caller is awaiting (i.e.,tradeResolversfor that asset is empty), the trade is buffered inpendingTrades. This buffer is only drained when the nextwatchTrades()call comes in (line 147–149). If a caller subscribes to market data viasubscribe()but then stops callingwatchTrades()(e.g., their streaming loop is paused, or they only calledwatchOrderBook), the per-asset trade buffer grows without bound. There is no max size, no TTL, and no eviction.OOM Estimate
Tradeobject: ~200 bytesSuggested Fix
Implement a ring buffer per asset: cap
pendingTrades.get(id)at e.g. 1000 entries; when the cap is reached, drop the oldest trade (shift()). Alternatively, expose amaxPendingTradesconfig option onPolymarketWebSocketConfig.Found by automated unbounded operations audit