PDA

View Full Version : Using QApplication::processEvents - possible hang under linux



SpanishJohn
6th January 2014, 15:59
Hi all, This is my first post in this forum so I hope it will help.

I have written a Qt based (4.8.3) GUI client that is used to configure a remote network switch. The GUI consists of a main thread that processes all the UI and a worker thread used to communicate with the server. All communication with the server is done using a bespoke API that in turn uses an RPC type protocol to communicate with the remote server. All API calls return an error code

So for example the client (GUI) may issue the following command

err = initializePhyPort(0)

The InitialisePhyPort API call is spawned from the main thread via a button press for example and is queued for execution by the worker thread which communicates with the server. Once the worker thread has executed the command the results are passed back to the main thread so that the GUI can be updated. here is the code for a API spawn operation:

The pre-processor constant WORKER_THREAD enables or disables the use of the worker thread


altr_ret_status_e api_operation_manager_t::add_new_and_wait(const api_data_t api_data)
{
m_mutex.lock();

m_api_op = new api_operation_t(api_data, false, false);
m_flush = false;

m_operations_pnd.push_back(m_api_op);

m_mutex.unlock();

backhaul_gui::set_wait_cursor();
backhaul_gui::set_status_busy();

altr_ret_status_e err = ALTR_RET_SUCCESS;

m_wait_op_busy = true;

#ifndef WORKER_THREAD

// This can take some time!!
run();

#endif

// Wait for the operation to complete - Note this may spawn further 'silent' ops
// Note that it is possible for the client to disconnect during this loop. In which case flush will be called
while(m_api_op->status() < OP_STATE_DONE)
{
Sleep(1);

QApplication::processEvents();
}

// Process any pending events (usually a MSG_BOX_EVENT_TYPE)
if (m_api_op->m_event)
{
QCoreApplication::postEvent(g_gui, m_api_op->m_event);
}

err = m_api_op->m_err;

// Remove anything in the done queue
m_mutex.lock();

for (api_vec_t::iterator it = m_operations_done.begin(); it != m_operations_done.end(); ++it)
{
delete *it;
}

m_operations_done.clear();

m_api_op = &m_default_op;

m_mutex.unlock();

// Signal that we are done
op_completed();

m_wait_op_busy = false;

return err;
}

So (in multi-threaded mode) the main thread will wait in a loop calling processEvents until the worker thread has done its thing. It will then return the error code. My problem is that under Linux I am seeing, very occasionaly, X-Server errors and I am almost certain, but not 100%, that the cause is due to the processEvents loop. When I run in single thread mode I do not see any X-Server errors.

Sorry for the somewhat long initial thread.

SJ

anda_skoa
7th January 2014, 00:18
Calling sleep is never a good idea in the main thread.

You can use a nested event loop to do event processing while "waiting" for something, e.g. a signal from the thread.

Way better, however, would be to always process the second half in a slot and emit the signal from either the thread or the code that is run when there is no thread.

Cheers,
_

SpanishJohn
7th January 2014, 13:35
Lesson learned. Be *very* careful when using QApplication ProcessEvents as it can hang on systems with signal recursion link Linux/X11.

Here is my new code that uses a QEventLoop


altr_ret_status_e api_operation_manager_t::wait_done(void)
{
altr_ret_status_e err = ALTR_RET_SUCCESS;

// Assert if we spawn another wait operation in the while loop below
check(m_wait_op_busy == false);

m_wait_op_busy = true;

#ifndef WORKER_THREAD

// This may take some time!!
run();

#endif

// Wait for the operation to complete - Note this may spawn further 'silent' ops
// Note that it is possible for the client to disconnect during this loop. In which case flush will be called
m_event_loop.exec();

if (m_flush)
{
// The client has been disconnected *or* the application wants to close
err = ALTR_RET_CLIENT_CLOSED;

if (!api_layer_t::active())
{
// The application wants to close
THROW_EXCEPTION_TRAP
}
}
else
{
// Process any pending events (usually a MSG_BOX_EVENT_TYPE)
if (m_api_op->m_event)
{
QCoreApplication::postEvent(g_gui, m_api_op->m_event);
}

err = m_api_op->m_err;

{
MUTEX_LOCKER locker(&m_mutex);

// Remove anything in the done queue
for (api_vec_t::iterator it = m_operations_done.begin(); it != m_operations_done.end(); ++it)
{
delete *it;
}

m_operations_done.clear();

m_api_op = &m_default_op;
}

// Signal that we are done
op_completed();
}

m_wait_op_busy = false;

return err;
}

anda_skoa
7th January 2014, 16:53
Couldn't you also move the eventLoop.exec into an #else case of the ifdef?
I.e. if you call run() directly, do you still need to wait for something?

Cheers,
_

SpanishJohn
9th January 2014, 12:43
Hi, If the worker thread is enabled then run() *is* the worker thread. The worker thread executes the queued commands and for each command the main thread will wait via the QEventLoop exec(). If the worker thread is disabled, then run() must be called explicitly, but the QEventLoop is still necessary as each command requires a GUI update phase (signalled from run()). At the end of the GUI update phase the QEventLoop quit() method is called.

Anyway, most importantly, the use of the QEventLoop has solved my X11 problems that I experienced using QApplication ProcessEvents :)

Regards, SJ