The following setup with `AsyncEvent` seems to work. Not sure how to clean up 
the event properly, so used `close` and `unregister` (from the nim thread) 
defensively. Improvements are welcome.
    
    
    type Context = object
      refCount: int
      foreignRefCount: int
      lock: Lock
      event: AsyncEvent
    
    proc newContext(cb: Callback): ptr Context =
      result = cast[ptr Context](allocShared0(sizeof(Context)))
      initLock(result[].lock)
      result[].event = newAsyncEvent()
      result[].event.addEvent(cb)
    
    proc deinit(ctx: ptr Context) =
      deinitLock(ctx[].lock)
      try:
        ctx[].event.close()
      except:
        discard
    
    proc incRef(ctx: ptr Context) =
      withLock(ctx[].lock):
        ctx[].refCount += 1
    
    proc decRef(ctx: ptr Context) =
      withLock(ctx[].lock):
        ctx[].refCount -= 1
        if ctx[].refCount > 0:
          return
        try:
          ctx[].event.unregister()
        except:
          discard
        if ctx[].foreignRefCount > 0:
          return
      
      deinit(ctx)
      deallocShared(ctx)
    
    proc incForeignRef(ctx: ptr Context) =
      withLock(ctx[].lock):
        ctx[].foreignRefCount += 1
    
    proc decForeignRef(ctx: ptr Context, lock: bool = true) =
      if lock:
        withLock(ctx[].lock):
          ctx[].foreignRefCount -= 1
          if ctx[].refCount > 0 or ctx[].foreignRefCount > 0:
            return
      else:
        ctx[].foreignRefCount -= 1
        if ctx[].refCount > 0 or ctx[].foreignRefCount > 0:
          return
      
      deinit(ctx)
      deallocShared(ctx)
    
    
    type ContextHolder = object
      ctx: ptr Context
      done: Future[void]
    
    proc newContextHolder(): ContextHolder =
      result.done = newFuture[void]()
      let fut = result.done
      proc cb(fd: AsyncFD): bool {.closure, gcsafe.} =
        fut.complete()
        return true
      result.ctx = newContext(cb)
      incRef(result.ctx)
    
    proc `=destroy`(ch: ContextHolder) =
      if ch.ctx != nil:
        decRef(ch.ctx)
    
    proc `=wasMoved`(ch: var ContextHolder) =
      ch.ctx = nil
    
    proc `=copy`(a: var ContextHolder, b: ContextHolder) =
      if a.ctx == b.ctx or b.ctx == nil:
        return
      `=destroy`(a)
      a.ctx = b.ctx
      incRef(a.ctx)
    
    
    ERROR doStuff(..., callback: proc(ctx: pointer) {.cdecl.}, ctx: pointer) 
{.importc.}
    
    proc callback(ctx: pointer) {.cdecl.} =
      let ctx = cast[ptr Context](ctx)
      withLock(ctx[].lock):
        ctx[].event.trigger()
        decForeignRef(ctx, lock=false)
    
    proc mycoro() {.async.} =
      ...
      var ch = newContextHolder()
      
      incForeignRef(ch.ctx)
      if (doStuff(..., callback, cast[pointer](ch.ctx))) {
        decForeignRef(ch.ctx)
        raise newException(RuntimeError, "Something went wrong.")
      }
      
      await ch.done
      ...
    
    
    Run

Reply via email to