How to read from a process stdout without it blocking?

Ive launched a process and set its output mode to .Pipe. Now I want to read whatever was printed without it blocking. How do I do that?

Context: I am trying to make a gui that calls to a cli whose output I want to show in realtime.

There are several approaches to non-blocking I/O. What you probably want to do is to use poll or select to get notified when the input is ready to read or after a timeout. std.io.poll will work here.

2 Likes

Huh okay I knew std.io.poll existed, I just thought that it wouldnt work here for some reason, and hoped that if it did that there would be a nicer way.

1 Like

Non-blocking I/O in low-level languages is a bit of a pain in the ass both because blocking I/O is simpler and because there is a wealth of incompatible APIs:

  • basically everywhere: select, poll
  • linux: epoll, /dev/poll, io_uring
  • bsds: kqueue
  • windows: io completion ports
  • userspace abstractions: aio, libuv, libev, libevent, many many more
1 Like

Yea Im not too familiar with the topic, but i have heard some of those words and the sheer amount of them scared me off every digging deeper regarding that topic.

I was hoping that someone would inform me about the forgotten child, something like a method of file that just does something like this (this was from a school project where my glass used java and my teacher let me use zig), just in a simpler way.

Speaking of which: Why is poller such a complex struct instead of just a function? Is it a code smell to use it the way I did there? Why the ability to poll multiple files with just one poller?

Why is poller such a complex struct instead of just a function?

I believe it’s mainly for convenience.

Is it a code smell to use it the way I did there?

I don’t think so, for your application it seems fine.

Why the ability to poll multiple files with just one poller?

Many applications like servers need to be notified of many file descriptors.

1 Like

Just an addition. “Non-blocking” I/O in literal sense is when you set O_NONBLOCK flag for a file descriptor (socket for ex.) and then do read() or write() in a loop (with optional small pause), checking if errno is EWOULDBLOCK/EAGAIN in case when these calls return -1. Of course, this is a CPU hog, but such an approach is quite applicable when you know for sure that data is almost always available.

Also O_NONBLOCK is often set even when using epoll and alike because there may be “spurious” POLLIN notifications. To deal with them you

  • either set O_NONBLOCK``and check for EAGAIN` as when doing “pure” non-blocking i/o
  • or determine how much data is available (ioctl(FIONREAD)) and if it is zero, do not perform a read()
2 Likes

You are technically correct! The best kind of correct!

How do I do the O_NONBLOCK thing? Im pretty sure ive done it before but just dont know how and where.

Something like this. There non-blocking mode is used for asynchronous tcp-connect.

  • get flags via fcntl(F_GETFL)
  • add NONBLOCK with |= to them
  • set flags via fcntl(F_SETFL)
1 Like

Alright thank you!

Wait theres no way this is crossplatform is there?

No it cant be.

I need this to be crossplatform (windows and linux)

Then select() is probably your choice (see also this comment). But I am not sure if this is really good advice.

I ended up just using std.io.poll which to my surprise actually worked.

It surprised me because just calling read on the stream just blocked until the execution of the subtask was fully over, I thought that this would mean that polling for input would also just skip until the subtask is fully done, leading to non-realtime output. TIL: it does give me realtime output.