Location
core/src/exchanges/polymarket/websocket.ts:199
Code
private userCallbacks: UserChannelCallback[] = [];
async watchUserFills(conditionIds: string[], callback: UserChannelCallback): Promise<void> {
// ...
this.userCallbacks.push(callback); // ← no dedup, no max size
this.userConditionIds = [...new Set([...this.userConditionIds, ...conditionIds])];
if (this.userWs) {
// Already connected — re-subscribe
this.sendUserSubscription(creds);
return;
}
await this.connectUserChannel(creds);
}
On every incoming user-channel message, all callbacks are invoked:
for (const cb of this.userCallbacks) {
try { cb(event); } catch (e) { ... }
}
Growth Pattern
watchUserFills() appends the caller's callback to userCallbacks on every invocation without deduplication. The only removal path is unwatchUserFills(), which replaces the entire array with []. If a caller:
- registers multiple callbacks for different condition sets (e.g., one callback per market)
- calls
watchUserFills inside a reconnect or retry loop
- holds a reference to a
PolymarketExchange instance that reconnects internally
…then userCallbacks grows linearly with each call. On every last_trade_price-equivalent user event, the inner for (const cb of this.userCallbacks) loop iterates all accumulated callbacks, many of which may be duplicates doing redundant work.
OOM Estimate
- Each callback closure: ~1–4 KB depending on captured scope
- At 1
watchUserFills call per minute over an 8-hour trading session: 480 callbacks × 4 KB = ~2 MB
- If a reconnect loop re-calls
watchUserFills at 1 Hz: 3,600 callbacks/hour × 4 KB = ~14 MB/hour
- CPU impact: O(n) callback dispatch on every user event; at 100 trades/min with 3,600 callbacks: 360,000 callback invocations/min
- Not an immediate OOM risk but causes quadratic CPU growth and memory creep over long sessions
Suggested Fix
Deduplicate callbacks by identity before pushing: if (!this.userCallbacks.includes(callback)) this.userCallbacks.push(callback). Alternatively, use a Set<UserChannelCallback> instead of an array to prevent duplicates automatically. Add a maxCallbacks guard as a safety net.
Found by automated unbounded operations audit
Location
core/src/exchanges/polymarket/websocket.ts:199Code
On every incoming user-channel message, all callbacks are invoked:
Growth Pattern
watchUserFills()appends the caller's callback touserCallbackson every invocation without deduplication. The only removal path isunwatchUserFills(), which replaces the entire array with[]. If a caller:watchUserFillsinside a reconnect or retry loopPolymarketExchangeinstance that reconnects internally…then
userCallbacksgrows linearly with each call. On everylast_trade_price-equivalent user event, the innerfor (const cb of this.userCallbacks)loop iterates all accumulated callbacks, many of which may be duplicates doing redundant work.OOM Estimate
watchUserFillscall per minute over an 8-hour trading session: 480 callbacks × 4 KB = ~2 MBwatchUserFillsat 1 Hz: 3,600 callbacks/hour × 4 KB = ~14 MB/hourSuggested Fix
Deduplicate callbacks by identity before pushing:
if (!this.userCallbacks.includes(callback)) this.userCallbacks.push(callback). Alternatively, use aSet<UserChannelCallback>instead of an array to prevent duplicates automatically. Add amaxCallbacksguard as a safety net.Found by automated unbounded operations audit