Non blocking pipe read from a child process
What is the correct way of performing a non blocking read from a pipe? I need the result from a child process, but I do not know when the child process will be done. So I need to poll periodically? Is this correct?
Also in the code below I try to guard against partial reads. I new line means the child process has sent the complete output and it means a complete stop of data transmission. But what if the read occurred before the new line (before the end of the whole transmission). So one solution is buffering until the new line?
#include <sys/time.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
FILE *fp;
int fd;
char buf[64]; /* temp read buffer */
char line[256]; /* accumulate full line */
int linepos = 0;
fp = popen("nxselect", "r");
if (!fp) {
printf("Failed to start nxselect\n");
return 1;
}
fd = fileno(fp);
printf("Waiting for user to select a file...\n");
while (1) {
fd_set set;
struct timeval tv;
int rv, n, i;
FD_ZERO(&set);
FD_SET(fd, &set);
tv.tv_sec = 0;
tv.tv_usec = 50000; /* 50 ms */
rv = select(fd + 1, &set, NULL, NULL, &tv);
if (rv > 0 && FD_ISSET(fd, &set)) {
/* Read whatever is available */
n = read(fd, buf, sizeof(buf));
if (n > 0) {
for (i = 0; i < n; i++) {
char c = buf[i];
if (linepos < sizeof(line) - 1)
line[linepos++] = c;
/* newline means full result ready */
if (c == '\n') {
line[linepos] = '\0';
printf("Selected file: %s", line);
pclose(fp);
return 0;
}
}
}
else {
/* nxselect closed pipe without output */
printf("nxselect terminated unexpectedly.\n");
break;
}
}
/* No data → keep waiting */
}
pclose(fp);
return 0;
}
Hello @toncho11,
What is the correct way of performing a non blocking read from a pipe? I need the result from a child process, but I do not know when the child process will be done. So I need to poll periodically? Is this correct?
No, basically this is a heck of a lot of extra code, when only a few lines are needed to read a result from the child process.
First, the pipe isn't in non-blocking mode, so regardless of whether waiting on a read directly, or waiting in select then doing a read, the read will hang if no data is present, otherwise it will return whatever data is present, up to the size of the read buffer passed.
Given that, the entire routine can be basically rewritten to use something like:
fp = popen("nxselect", "r");
...
/* wait for newline-terminated string from pipe */
if (!fgets(buf, sizeof(buf), fp)) return ERR;
/* buf now has \n\0 terminated string */
pclose(fp);
I need a non blocking way of doing it. How can I do that? This is the main question, not simplicity.
Once I execute popen it could take 1 minute for the user to select a file, I can not block the parent app for 1 minute. What do I do? A signal maybe? It is a question of inter process communication. The child needs to return a string at some point to the parent (a path to a file).
I need a non blocking way of doing it. How can I do that?
In that case, your method above of using select should work. Does the above code work?
You may be able to replace all the code following the select with an fgets as I proposed, there shouldn't be any reason to use non-blocking, as the single application write to the pipe (followed by fflush) will write all the contents. Then select will set the file descriptor as ready for reading, and a single fgets/read will read all the data. Don't worry about the characters being returned broken into two read/writes, that won't happen as long as the text is < 80 bytes long.
I need to go buy the Christmas tree or my family will kill me. This is my use case in general:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include "nano-X.h"
/* State used by polling function */
static FILE *nx_fp = NULL;
static int nx_fd = -1;
static int nxselect_running = 0;
/* ------------------------------------------ */
/* Asynchronous polling for nxselect result */
/* ------------------------------------------ */
void poll_for_nxselect_result(void)
{
fd_set set;
struct timeval tv;
FD_ZERO(&set);
FD_SET(nx_fd, &set);
tv.tv_sec = 0; /* Non-blocking */
tv.tv_usec = 0;
int rv = select(nx_fd + 1, &set, NULL, NULL, &tv);
if (rv > 0 && FD_ISSET(nx_fd, &set)) {
char buf[256];
/* Read entire line (non-blocking because select says ready) */
if (fgets(buf, sizeof(buf), nx_fp)) {
buf[strcspn(buf, "\r\n")] = '\0';
printf("path received: %s\n", buf);
} else {
printf("nxselect closed (no path)\n");
}
pclose(nx_fp);
nx_fp = NULL;
nx_fd = -1;
nxselect_running = 0; /* stop polling */
}
}
int main(void)
{
if (GrOpen() < 0) {
printf("GrOpen failed\n");
return 1;
}
GR_WINDOW_ID win = GrNewWindowEx(
GR_WM_PROPS_APPWINDOW,
"nx async example",
GR_ROOT_WINDOW_ID,
20, 20, 300, 200,
WHITE);
GrSelectEvents(win, GR_EVENT_MASK_EXPOSURE | GR_EVENT_MASK_CLOSE_REQ);
GrMapWindow(win);
int finished = 0;
while (!finished)
{
GR_EVENT ev;
if (GrGetNextEventTimeout(&ev, 50) == GR_TRUE) {
switch (ev.type) {
case GR_EVENT_TYPE_EXPOSURE:
printf("EXPOSE event\n");
if (!nxselect_running) {
nx_fp = popen("nxselect", "r");
if (!nx_fp) {
printf("Failed to start nxselect\n");
} else {
nx_fd = fileno(nx_fp);
nxselect_running = 1;
printf("nxselect launched\n");
}
}
break;
case GR_EVENT_TYPE_CLOSE_REQ:
finished = 1;
break;
}
}
/* Poll outside event handling */
if (nxselect_running)
poll_for_nxselect_result();
}
GrClose();
return 0;
}
So:
- I need to use in nxselect:
printf("%s\n", path); /* buffered */
fflush(stdout); /* REQUIRED */
or
write(1, "/full/path/to/file\n", length);
- And select() + fgets() should be OK because the full line is already present when select() returns
?
Yes, either way write or printf, then select and fgets should work.
Thanks. Seems to work for now. The idea was to have atomic writes and reads.