tchap tchap - 25 days ago 7
C++ Question

Blocking call to read a pipe

I'm using Qt to create a small application that displays a GUI and accepts input from a pipe.

If the pipe is not created (or, as I understand, if there's no writer), the call to

fopen
blocks, and even thought it's supposedly called after the
show()
function, the UI is not shown.

How can I display the UI, and then call
fopen
and the related code? I don't care if
fopen
blocks as long as my window is on screen beforehand.

I have tried using something like
connect(this, SIGNAL(window_loaded), this, SLOT(setupListener()));
but the behavior remains the same.

Any hints ? Thanks !

main.cpp

#include <QApplication>

#include "metadataWindow.h"

#include <sys/time.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
metadataWindow window;
window.showFullScreen();
window.setupListener();

return app.exec();
}


metadataWindow.cpp

metadataWindow::metadataWindow(QWidget *parent) : QWidget(parent)
{
this->setupUI(); // not shown here, but just basic QLabel stuff
}

void metadataWindow::setupListener()
{
const char *metadata_file = "/tmp/my-pipe-file";

// vvvvv This here is blocking vvvvvv
FILE *fd = fopen(metadata_file, "r");

pipe = new QTextStream(fd);

streamReader = new QSocketNotifier(fileno(fd), QSocketNotifier::Read, qApp);
QObject::connect(streamReader, SIGNAL(activated(int)), this, SLOT(onData()));
streamReader->setEnabled(true);
}

Answer

X is an asynchronous, message-based protocol. An X display server, and an X client program are constantly exchanging messages. An X client program doesn't just push some kind of a virtual button, draw its window, and calls it a day, until it wants to change something on the window. The only time there are no messages being exchanged between the display server and a client program is when absolutely nothing happens on the display. No mouse pointer movement. No display activity whatsoever.

The task of showing a window involves a number of multiple steps, in sequence. The actual window object itself gets created. All subwindows get created. All windows get mapped. Mapping the window results in the X server sending a series of exposure events to the client program, in response to which the client program is responsible for rendering the exposed part of the window. All this is done as a sequence of hundreds of messages exchanged between the X display server, and an X client program.

That's what the QApplication::exec() call does. It enters Qt's main event loop, with the Qt library processing X display events accordingly. Until the event loop runs, there will not be any visible display changes.

The correct design pattern, when working with an event-based infrastructure like X/Qt, is also an event-based approach. You have two basic options.

  1. Execute your blocking application logic in a new thread, independently of the main execution thread that enters Qt's event loop. This bypasses and side-steps the need to conform to an event-driven design pattern, and makes it possible to do pretty much an ordinary program would do, without bothering Qt.

  2. Use an event-driven model, with non-blocking file descriptors, for your own code too. The fopen() library call cannot be used. Instead, the pipe would be open()ed in non-blocking mode, and when the other side of the filesystem pipe is opened, the pipe will be selectable for writing. Read the manual pages for open() and poll(), for more information. Finally read Qt's documentation for the QSocketNotifier class, which explains how to have the Qt library also monitor for events on your own file descriptors, as part of its main event loop, and invoke your code to handle the task of reading and writing them.

Of course, a hybrid approach, of using both execution threads, and socket notifiers, is also possible. The important point is to understand how the process should work correctly, and never write any code that blocks Qt's main event loop.