We had a bug on MacOS and realized that in the following code the second readline blocks whereas it doesn't block on Linux.
import asyncdispatch, asyncfile
proc myTask() {.async.} =
var inputFile = openAsync("/dev/stdin", fmRead)
var inputFuture = inputFile.readline()
while true:
let input = await inputFuture
echo "Got input: ", input
echo "The next call should never block"
inputFuture = inputFile.readLine()
echo "This message should show instantly"
proc main() =
asyncCheck myTask()
runForever()
main()
Output on Linux:
$ ./test
hallo
Got input: hallo
The next call should never block
This message should show instantly
welt
Got input: welt
The next call should never block
This message should show instantly
Output on MacOS:
$ ./test
hallo
Got input: hallo
The next call should never block
welt
This message should show instantly
Got input: welt
The next call should never block
If we replace openAsync("/dev/stdin", fmRead) with newAsyncFile(AsyncFD(0)) it blocks on Linux, too.
I expected that readline would never block and returns a Future immediately.
Nim version: 0.20.0
If there is a better way to read stdin asynchronously, I would be really interested.
Yeah, you cannot read stdin asynchronously like this.
You have to use a little bit of a workaround, currently it looks like this: https://github.com/dom96/nim-in-action-code/blob/master/Chapter3/ChatApp/src/client.nim#L42-L50. Eventually once awaiting a spawned proc becomes possible it'll be much easier (and far more efficient) (CC @rayman22201)
Thanks for the workaround, unfortunately it didn't work for me in combination with other async tasks.
But it helped me develop an async readline function:
proc asyncReadline(): Future[string] =
let event = newAsyncEvent()
let future = newFuture[string]("asyncReadline")
proc readlineBackground(event: AsyncEvent): string =
result = stdin.readline()
event.trigger()
let flowVar = spawn readlineBackground(event)
proc callback(fd: AsyncFD): bool =
future.complete(^flowVar)
true
addEvent(event, callback)
return future
That's actually a very nice way to implement this. When I implemented the code above we didn't have async events. This seems like it could be a relatively easy way to implement await support for FlowVar's (the return type of spawned procs).
What do you think @rayman22201 ?
It's a clean way to implement this.
AsyncEvent seems to make adding custom event loop triggers much easier.
I think they are efficient-ish?
I could definitely see extending this to the general case of async spawn.
It also illustrates the reason why I think flowVars are superfluous....
The code is essentially a wrapper for a thread future (flowVar) inside a regular future.
Why can't spawn just do this internally and return a normal Future directly?
I have a similar issue when using asyncfile.readAll() (and readLine too), but I'm reading an actual file. Here is my example:
import asyncdispatch, asyncfile
proc readFiles(): Future[string] {.async.} =
var file = openAsync("numbers.txt", fmRead)
try:
echo "start reading"
let res = await file.readAll()
echo "end reading"
return res
finally:
file.close()
echo "call readFiles()"
let f = readFiles()
echo "waiting for result..."
let res = waitFor f
echo "done"
The numbers.txt file was generated with seq 1 10000 > numbers.txt. I've tried changing the file size and did not seem to make a difference.
nim c -r example.vim
# or docker
docker run -it --rm -v $PWD/numbers.txt:/numbers.txt -v $PWD/example.nim:/example.nim nimlang/nim:latest nim c -r /example.nim
I get the following output on both Mac OS and Linux:
call readFiles()
start reading
end reading
waiting for result...
done
Coming from NodeJS, I expected the output to be:
call readFiles()
start reading
waiting for result...
end reading
done
Is there something I'm missing?
@jeremija
Yeah, same issue going on. All of the asyncfile apis have this problem until we get multithreaded async working. (Something I'm actively working on)
until then, you can use a similar work around. This should give the expected output:
(Note: it uses threads, so you need to compile with --threads:on)
import asyncdispatch, asyncfile, threadpool
proc asyncReadAll(path: string): Future[string] =
let event = newAsyncEvent()
let future = newFuture[string]("asyncReadline")
proc readlineBackground(path: string, event: AsyncEvent): string =
try:
result = cast[string](readFile(path))
except:
# unfortunately we don't have a good way to get exceptions accross thread boundaries
result = getCurrentExceptionMsg()
finally:
event.trigger()
let flowVar = spawn readlineBackground(path, event)
proc callback(fd: AsyncFD): bool =
future.complete(^flowVar)
true
addEvent(event, callback)
return future
proc readFiles(): Future[string] {.async.} =
echo "start reading"
let res = await asyncReadAll("numbers.txt")
echo "end reading"
return res
echo "call readFiles()"
let f = readFiles()
echo "waiting for result..."
let res = waitFor f
echo "done"
I'm seeing this same behavior in v1.2.4. This means we don't have real non-blocking file IO with async/await. It seems the documentation should be updated to explain this. I was expecting behavior similar to node.js as well.
Most helpful comment
@jeremija
Yeah, same issue going on. All of the asyncfile apis have this problem until we get multithreaded async working. (Something I'm actively working on)
until then, you can use a similar work around. This should give the expected output:
(Note: it uses threads, so you need to compile with
--threads:on)