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