Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible memory leak triggered by async #7532

Closed
dom96 opened this issue Apr 6, 2018 · 4 comments
Closed

Possible memory leak triggered by async #7532

dom96 opened this issue Apr 6, 2018 · 4 comments
Labels
Async Everything related to Nim's async

Comments

@dom96
Copy link
Contributor

dom96 commented Apr 6, 2018

This is a fun one and I caught it thanks to my work on httpbeast.

I was able to systematically create a short sample reproducing the issue with no dependencies (except on the stdlib).

WARNING: This allocates memory very quickly.

import selectors, asyncdispatch
import future

type
  Data = object

type
  OnRequest* = proc (): Future[void] {.gcsafe.}

proc onRequestFutureComplete(theFut: Future[void],
                             selector: Selector[Data]) =
  discard

proc eventLoop(onRequest: OnRequest) =

  let selector = newSelector[Data]()

  let disp = getGlobalDispatcher() # This needs to be here <---

  while true:
    let fut = onRequest()
    if not fut.isNil:
      fut.callback =
        (theFut: Future[void]) =>
          (onRequestFutureComplete(theFut, selector))

when isMainModule:
  proc onRequestAsync(): Future[void] {.gcsafe.} =
    var retFuture255001 = newFuture[void]("onRequest")
    iterator onRequestIter255002(): FutureBase {.closure.} =
      complete(retFuture255001)

    var nameIterVar255005 = onRequestIter255002
    var next255007 = nameIterVar255005()

    return retFuture255001

  eventLoop(onRequestAsync)
Click to expand old reproduction that depends on httpbeast I wasn't able to reproduce this outside my project, so here is the code needed to reproduce it with ``httpbeast`` as a dependency:
import options, asyncdispatch, strutils

import httpbeast

proc onRequest(req: Request): Future[void] {.async.} =
  if req.httpMethod == some(HttpGet):
    case req.path.get()
    of "/":
      req.send("Hello World")
    else:
      req.send(Http404)

proc onRequestAsync(req: Request): Future[void] =
  var retFuture255001 = newFuture[void]("onRequest")
  iterator onRequestIter255002(): FutureBase {.closure.} =
    if req.httpMethod == some(HttpGet):
      case req.path.get()
      of "/":
        req.send("Hello World")
      else:
        req.send(Http404)
    complete(retFuture255001)

  var nameIterVar255005 = onRequestIter255002
  proc onRequest_continue255003() {.closure.} =
    try:
      if not nameIterVar255005.finished:
        var next255007 = nameIterVar255005()
        while (not next255007.isNil) and
            next255007.finished:
          next255007 = nameIterVar255005()
          if nameIterVar255005.finished:
            break
        if next255007 ==
            nil:
          if not retFuture255001.finished:
            let msg255009 = "Async procedure ($1) yielded `nil`, are you await\'ing a " &
                "`nil` Future?"
            raise newException(AssertionError, msg255009 % "onRequest")
        else:
          {.gcsafe.}:
            {.push, hint[ConvFromXtoItselfNotNeeded]: off.}
            next255007.callback = (proc () {.closure, gcsafe.})(onRequest_continue255003)
            {.pop.}
    except:
      if retFuture255001.finished:
        raise
      else:
        retFuture255001.fail(getCurrentException())

  onRequest_continue255003()
  return retFuture255001

run(onRequestAsync)

Tip: The onRequestAsync procedure is just what the {.async.} pragma produces out of the onRequest procedure defined above.

Here is how I solved it, PR will be coming soon to generate this code:

import options, asyncdispatch, strutils

import httpbeast

proc onRequestAsync(req: Request): Future[void] =
  var retFuture255001 = newFuture[void]("onRequest")
  iterator onRequestIter255002(): FutureBase {.closure.} =
    if req.httpMethod == some(HttpGet):
      case req.path.get()
      of "/":
        req.send("Hello World")
      else:
        req.send(Http404)
    # complete(retFuture255001)

  var nameIterVar255005 = onRequestIter255002
  proc onRequest_continue255003() {.closure.} =
    try:
      if not nameIterVar255005.finished:
        var next255007 = nameIterVar255005()
        while (not next255007.isNil) and
            next255007.finished:
          next255007 = nameIterVar255005()
          if nameIterVar255005.finished:
            break

        if likely(next255007 != nil):
          {.gcsafe.}:
            {.push, hint[ConvFromXtoItselfNotNeeded]: off.}
            next255007.callback = (proc () {.closure, gcsafe.})(onRequest_continue255003)
            {.pop.}
        else:
          let msg255009 = "Async procedure ($1) yielded `nil`, are you await\'ing a `nil` Future?"
          assert retFuture255001.finished, msg255009
    except:
      if retFuture255001.finished:
        raise
      else:
        retFuture255001.fail(getCurrentException())

  onRequest_continue255003()
  return retFuture255001

run(onRequestAsync)
@dom96 dom96 added Garbage Collection Async Everything related to Nim's async labels Apr 6, 2018
@Yardanico Yardanico assigned Yardanico and unassigned Yardanico Apr 7, 2018
@dom96
Copy link
Contributor Author

dom96 commented Apr 7, 2018

I played around with dumpNumberOfInstances, it seems that the Futures are not being deallocated.

[Heap] Future[system.void]: #48481; bytes: 4654176

@Araq
Copy link
Member

Araq commented Apr 11, 2018

That's not even a bug afaict. You call callback= which registers the future as "callsoon" as it's completed already. You then never give the event loop the chance to run these, so disp.callbacks continues to grow and it also keeps the futures alive (these are part of the closure's environment).

@dom96
Copy link
Contributor Author

dom96 commented Apr 14, 2018

dom96 added a commit to dom96/httpbeast that referenced this issue May 4, 2018
@Araq
Copy link
Member

Araq commented Aug 5, 2019

Fixed in httpbeast.

@Araq Araq closed this as completed Aug 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Async Everything related to Nim's async
Projects
None yet
Development

No branches or pull requests

3 participants