Fix memory leak in asyncdispatch.withTimeout by clearing losing callbacks (#25567)

withTimeout currently leaves the “losing” callback installed:

  - when fut finishes first, timeout callback remains until timer fires,
- when timeout fires first, fut callback remains on the wrapped future.

Under high-throughput use with large future payloads, this retains
closures/future references longer than needed and causes large transient
RSS growth.
This patch clears the opposite callback immediately once outcome is
decided, reducing retention without changing API behavior.
This commit is contained in:
vercingetorx
2026-03-01 13:11:18 -08:00
committed by GitHub
parent e69d672354
commit 9ed4077d9a

View File

@@ -1946,9 +1946,14 @@ proc withTimeout*[T](fut: Future[T], timeout: int): owned(Future[bool]) =
retFuture.fail(fut.error)
else:
retFuture.complete(true)
# Timeout side lost; drop its callback to avoid retaining closures/futures.
timeoutFuture.clearCallbacks()
timeoutFuture.callback =
proc () =
if not retFuture.finished: retFuture.complete(false)
if not retFuture.finished:
retFuture.complete(false)
# Wrapped future side lost; drop its callback to avoid retaining closures/futures.
fut.clearCallbacks()
return retFuture
proc accept*(socket: AsyncFD,