We can create template with like this:
template withFile(name: string, body: untyped) =
let fh = open(name, ...)
defer: close(fh)
block:
body
But when template contain await, the compiler will complain the 'await' is undeclared identifier. The code like this:
template withDb(body: untyped) =
let db = await open(...)
defer: close(db)
block:
body
Because a template cannot be async and the 'await' is not removed by macro async, so the compiler will find undeclared 'await', but is there any way to make the code above works? Thanks.
Yes, you can do this:
template withDb(body: untyped) =
let dbFut = open(...)
yield dbFut
let db = dbFut.read()
defer: close(db)
block:
body
Nim's 'async' macro rewrites the code so that it becomes iterable/etc where needed.
https://nim-lang.org/docs/asyncdispatch.html#17
I think what Dom's doing here is giving a slightly desugared version of what the 'async' macro would normally do for your code.
@dom96 Thanks. The code seems works, but it doesn't. db.close() seems executed before the code 'block', this is the error message:
asyncdispatch.nim(1492) waitFor
asyncdispatch.nim(1496) poll
asyncdispatch.nim(292) runOnce
Error: unhandled exception: No handles or timers registered in dispatcher. [ValueError]
So I must use the following code:
template withDb(body: untyped) =
let dbFut = open(...)
yield dbFut
let db = dbFut.read()
block:
body
close(db)
But the code above cannot deal with the exception raised from 'body', the code 'body' raise a exception, close(db) will not be executed. I cannot use try...finally either, the compiler will not pass.
So there is a really big problem, how to close the resource reliablely within a async proc?
You need to handle the exception: https://nim-lang.org/docs/asyncdispatch.html#asynchronous-procedures-handling-exceptions
(read will raise the exception if one exists, so you should do this before calling it)
The following code is extracted from asyncpg.
template withConnection*(pool: apgPool, conn, body: untyped) =
## Retrieves first available connection from pool, assign it
## to variable with name `conn`. You can use this connection
## inside withConnection code block.
mixin getFreeConnection
var connFuture = getFreeConnection(pool)
yield connFuture
var index = connFuture.read
block:
var conn = pool.connections[index]
body
pool.futures[index].complete()
When code in 'body' raise a exception, the last line "pool.future[index].complete()" will not be executed and the connection in the pool will be leaked. Wrapper the body in a proc is not possible when body contains async call itself. I have no idea how to make the above code works right.
@dom96 is right, it can be done like this:
macro withTran(db, body: untyped): untyped =
quote do:
block:
proc action(`db`: apgPoolConnection) {.async.} =
`body`
let fut = doTransaction(action)
yield fut
if fut.failed: raise fut.readError
The 'body' can be wrappered with an async proc!