xcb: lock-free event processing

For details how this works refer to the documentation in the patch.

The follow-up patches will switch to calling processXcbEvents() on every
event loop iteration. With the existing code that would mean frequent
locking of shared data (event queue). Acquiring a lock is fast, but
lock contention isn't. To avoid potential problems, reimplement xcb event
processing to be lock-free. Besides theoretical performance benefits,
this definitally improves code readability in qxcbconnection.cpp. Thanks
to Mikhail Svetkin for questioning the design of the existing code.

Done-with: Mikhail Svetkin <mikhail.svetkin@qt.io>
Change-Id: I935f2b6ca802580f5c80205aef7b2f9afc172d26
Reviewed-by: Mikhail Svetkin <mikhail.svetkin@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Gatis Paeglis 2018-08-31 13:03:47 +02:00
parent a880780ff9
commit 243c3044b6
12 changed files with 589 additions and 359 deletions

View File

@ -807,18 +807,18 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
{
QElapsedTimer timer;
timer.start();
QXcbEventQueue *queue = connection()->eventQueue();
do {
auto e = connection()->checkEvent([window, type](xcb_generic_event_t *event, int eventType) {
auto e = queue->peek([window, type](xcb_generic_event_t *event, int eventType) {
if (eventType != type)
return false;
if (eventType == XCB_PROPERTY_NOTIFY) {
auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
if (propertyNotify->window == window)
return true;
} else if (eventType == XCB_SELECTION_NOTIFY) {
return propertyNotify->window == window;
}
if (eventType == XCB_SELECTION_NOTIFY) {
auto selectionNotify = reinterpret_cast<xcb_selection_notify_event_t *>(event);
if (selectionNotify->requestor == window)
return true;
return selectionNotify->requestor == window;
}
return false;
});
@ -833,7 +833,7 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
// process other clipboard events, since someone is probably requesting data from us
auto clipboardAtom = atom(QXcbAtom::CLIPBOARD);
e = connection()->checkEvent([clipboardAtom](xcb_generic_event_t *event, int type) {
e = queue->peek([clipboardAtom](xcb_generic_event_t *event, int type) {
xcb_atom_t selection = XCB_ATOM_NONE;
if (type == XCB_SELECTION_REQUEST)
selection = reinterpret_cast<xcb_selection_request_event_t *>(event)->selection;

View File

@ -57,6 +57,7 @@
#include "qxcbglintegration.h"
#include "qxcbcursor.h"
#include "qxcbbackingstore.h"
#include "qxcbeventqueue.h"
#include <QSocketNotifier>
#include <QAbstractEventDispatcher>
@ -97,6 +98,7 @@ Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices")
Q_LOGGING_CATEGORY(lcQpaXInputEvents, "qt.qpa.input.events")
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
Q_LOGGING_CATEGORY(lcQpaEventReader, "qt.qpa.events.reader")
Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb") // for general (uncategorized) XCB logging
Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker")
Q_LOGGING_CATEGORY(lcQpaKeyboard, "qt.qpa.xkeyboard")
@ -550,8 +552,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
return;
}
m_reader = new QXcbEventReader(this);
m_reader->start();
m_eventQueue = new QXcbEventQueue(this);
xcb_extension_t *extensions[] = {
&xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id,
@ -618,12 +619,8 @@ QXcbConnection::~QXcbConnection()
#if QT_CONFIG(draganddrop)
delete m_drag;
#endif
if (m_reader && m_reader->isRunning()) {
sendConnectionEvent(QXcbAtom::_QT_CLOSE_CONNECTION);
m_reader->wait();
}
delete m_reader;
if (m_eventQueue)
delete m_eventQueue;
QXcbIntegration *integration = QXcbIntegration::instance();
// Delete screens in reverse order to avoid crash in case of multiple screens
@ -1226,151 +1223,6 @@ void QXcbConnection::addPeekFunc(PeekFunc f)
m_peekFuncs.append(f);
}
qint32 QXcbConnection::generatePeekerId()
{
qint32 peekerId = m_peekerIdSource++;
m_peekerToCachedIndex.insert(peekerId, 0);
return peekerId;
}
bool QXcbConnection::removePeekerId(qint32 peekerId)
{
if (!m_peekerToCachedIndex.contains(peekerId)) {
qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
return false;
}
m_peekerToCachedIndex.remove(peekerId);
if (m_peekerToCachedIndex.isEmpty()) {
m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
m_peekerIndexCacheDirty = false;
}
return true;
}
bool QXcbConnection::peekEventQueue(PeekerCallback peeker, void *peekerData,
PeekOptions option, qint32 peekerId)
{
bool peekerIdProvided = peekerId != -1;
if (peekerIdProvided && !m_peekerToCachedIndex.contains(peekerId)) {
qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
return false;
}
bool peekFromCachedIndex = option.testFlag(PeekOption::PeekFromCachedIndex);
if (peekFromCachedIndex && !peekerIdProvided) {
qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
return false;
}
if (peekerIdProvided && m_peekerIndexCacheDirty) {
// When the main event loop has flushed the buffered XCB events into the window
// system event queue, the cached indices are not valid anymore and need reset.
auto it = m_peekerToCachedIndex.begin();
while (it != m_peekerToCachedIndex.constEnd()) {
(*it) = 0;
++it;
}
m_peekerIndexCacheDirty = false;
}
qint32 peekerIndex = peekFromCachedIndex ? m_peekerToCachedIndex.value(peekerId) : 0;
qint32 startingIndex = peekerIndex;
bool result = false;
m_mainEventLoopFlushedQueue = false;
QXcbEventArray *eventqueue = m_reader->lock();
if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) {
qCDebug(lcQpaPeeker, "[%d] peeker index: %d | mode: %s | queue size: %d", peekerId,
peekerIndex, peekFromCachedIndex ? "cache" : "start", eventqueue->size());
}
while (peekerIndex < eventqueue->size() && !result && !m_mainEventLoopFlushedQueue) {
xcb_generic_event_t *event = eventqueue->at(peekerIndex++);
if (!event)
continue;
if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) {
QString debug = QString((QLatin1String("[%1] peeking at index: %2")))
.arg(peekerId).arg(peekerIndex - 1);
printXcbEvent(lcQpaPeeker(), debug.toLatin1(), event);
}
// A peeker may call QCoreApplication::processEvents(), which has two implications:
// 1) We need to make the lock available for QXcbConnection::processXcbEvents(),
// otherwise we will deadlock;
// 2) QXcbConnection::processXcbEvents() will flush the queue we are currently
// looping through;
m_reader->unlock();
result = peeker(event, peekerData);
m_reader->lock();
}
m_reader->unlock();
if (peekerIdProvided && peekerIndex != startingIndex && !m_mainEventLoopFlushedQueue) {
auto it = m_peekerToCachedIndex.find(peekerId);
// Make sure that a peeker callback did not remove the peeker id
if (it != m_peekerToCachedIndex.constEnd())
(*it) = peekerIndex;
}
return result;
}
QXcbEventReader::QXcbEventReader(QXcbConnection *connection)
: m_connection(connection)
{
}
void QXcbEventReader::start()
{
connect(this, &QXcbEventReader::eventPending, m_connection, &QXcbConnection::processXcbEvents, Qt::QueuedConnection);
connect(this, &QXcbEventReader::finished, m_connection, &QXcbConnection::processXcbEvents);
QThread::start();
}
void QXcbEventReader::registerEventDispatcher(QAbstractEventDispatcher *dispatcher)
{
// Flush the xcb connection before the event dispatcher is going to block.
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_connection, &QXcbConnection::flush);
}
void QXcbEventReader::run()
{
xcb_generic_event_t *event;
while (m_connection && (event = xcb_wait_for_event(m_connection->xcb_connection()))) {
m_mutex.lock();
addEvent(event);
while (m_connection && (event = xcb_poll_for_queued_event(m_connection->xcb_connection())))
addEvent(event);
m_mutex.unlock();
emit eventPending();
}
m_mutex.lock();
for (int i = 0; i < m_events.size(); ++i)
free(m_events.at(i));
m_events.clear();
m_mutex.unlock();
}
void QXcbEventReader::addEvent(xcb_generic_event_t *event)
{
if ((event->response_type & ~0x80) == XCB_CLIENT_MESSAGE
&& (reinterpret_cast<xcb_client_message_event_t *>(event))->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
m_connection = 0;
m_events << event;
}
QXcbEventArray *QXcbEventReader::lock()
{
m_mutex.lock();
return &m_events;
}
void QXcbEventReader::unlock()
{
m_mutex.unlock();
}
void QXcbConnection::setFocusWindow(QWindow *w)
{
m_focusWindow = w ? static_cast<QXcbWindow *>(w->handle()) : nullptr;
@ -1397,31 +1249,6 @@ void QXcbConnection::ungrabServer()
xcb_ungrab_server(m_connection);
}
void QXcbConnection::sendConnectionEvent(QXcbAtom::Atom a, uint id)
{
xcb_client_message_event_t event;
memset(&event, 0, sizeof(event));
const xcb_window_t eventListener = xcb_generate_id(m_connection);
xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup);
xcb_screen_t *screen = it.data;
xcb_create_window(m_connection, XCB_COPY_FROM_PARENT,
eventListener, screen->root,
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
screen->root_visual, 0, 0);
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.sequence = 0;
event.window = eventListener;
event.type = atom(a);
event.data.data32[0] = id;
xcb_send_event(xcb_connection(), false, eventListener, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
xcb_destroy_window(m_connection, eventListener);
xcb_flush(xcb_connection());
}
xcb_timestamp_t QXcbConnection::getTimestamp()
{
// send a dummy event to myself to get the timestamp from X server.
@ -1433,12 +1260,10 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
connection()->flush();
xcb_generic_event_t *event = nullptr;
// lets keep this inside a loop to avoid a possible race condition, where
// reader thread has not yet had the time to acquire the mutex in order
// to add the new set of events to its event queue
while (!event) {
connection()->sync();
event = checkEvent([window, dummyAtom](xcb_generic_event_t *event, int type) {
event = eventQueue()->peek([window, dummyAtom](xcb_generic_event_t *event, int type) {
if (type != XCB_PROPERTY_NOTIFY)
return false;
auto propertyNotify = reinterpret_cast<xcb_property_notify_event_t *>(event);
@ -1546,10 +1371,6 @@ static inline bool isXIType(xcb_generic_event_t *event, int opCode, uint16_t typ
return e->event_type == type;
}
#endif
static inline bool isValid(xcb_generic_event_t *event)
{
return event && (event->response_type & ~0x80);
}
/*! \internal
@ -1563,22 +1384,18 @@ static inline bool isValid(xcb_generic_event_t *event)
3) Or add public API to Qt for disabling event compression QTBUG-44964
*/
bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex, QXcbEventArray *eventqueue) const
bool QXcbConnection::compressEvent(xcb_generic_event_t *event) const
{
uint responseType = event->response_type & ~0x80;
int nextIndex = currentIndex + 1;
if (responseType == XCB_MOTION_NOTIFY) {
// compress XCB_MOTION_NOTIFY notify events
for (int j = nextIndex; j < eventqueue->size(); ++j) {
xcb_generic_event_t *next = eventqueue->at(j);
if (!isValid(next))
continue;
if (next->response_type == XCB_MOTION_NOTIFY)
return true;
}
return false;
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
[](xcb_generic_event_t *, int type) {
return type == XCB_MOTION_NOTIFY;
});
}
#if QT_CONFIG(xcb_xinput)
// compress XI_* events
if (responseType == XCB_GE_GENERIC) {
@ -1588,50 +1405,44 @@ bool QXcbConnection::compressEvent(xcb_generic_event_t *event, int currentIndex,
// compress XI_Motion
if (isXIType(event, m_xiOpCode, XCB_INPUT_MOTION)) {
#if QT_CONFIG(tabletevent)
auto *xdev = reinterpret_cast<xcb_input_motion_event_t *>(event);
auto xdev = reinterpret_cast<xcb_input_motion_event_t *>(event);
if (!QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents) &&
const_cast<QXcbConnection *>(this)->tabletDataForDevice(xdev->sourceid))
return false;
#endif // QT_CONFIG(tabletevent)
for (int j = nextIndex; j < eventqueue->size(); ++j) {
xcb_generic_event_t *next = eventqueue->at(j);
if (!isValid(next))
continue;
if (isXIType(next, m_xiOpCode, XCB_INPUT_MOTION))
return true;
}
return false;
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
[this](xcb_generic_event_t *next, int) {
return isXIType(next, m_xiOpCode, XCB_INPUT_MOTION);
});
}
// compress XI_TouchUpdate for the same touch point id
if (isXIType(event, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE)) {
auto *touchUpdateEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(event);
auto touchUpdateEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(event);
uint32_t id = touchUpdateEvent->detail % INT_MAX;
for (int j = nextIndex; j < eventqueue->size(); ++j) {
xcb_generic_event_t *next = eventqueue->at(j);
if (!isValid(next))
continue;
if (isXIType(next, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE)) {
auto *touchUpdateNextEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(next);
if (id == touchUpdateNextEvent->detail % INT_MAX)
return true;
}
}
return false;
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
[this, &id](xcb_generic_event_t *next, int) {
if (!isXIType(next, m_xiOpCode, XCB_INPUT_TOUCH_UPDATE))
return false;
auto touchUpdateNextEvent = reinterpret_cast<xcb_input_touch_update_event_t *>(next);
return id == touchUpdateNextEvent->detail % INT_MAX;
});
}
return false;
}
#endif
if (responseType == XCB_CONFIGURE_NOTIFY) {
// compress multiple configure notify events for the same window
for (int j = nextIndex; j < eventqueue->size(); ++j) {
xcb_generic_event_t *next = eventqueue->at(j);
if (isValid(next) && next->response_type == XCB_CONFIGURE_NOTIFY
&& reinterpret_cast<xcb_configure_notify_event_t *>(next)->event == reinterpret_cast<xcb_configure_notify_event_t *>(event)->event)
{
return true;
}
}
return false;
return m_eventQueue->peek(QXcbEventQueue::PeekRetainMatch,
[event](xcb_generic_event_t *next, int type) {
if (type != XCB_CONFIGURE_NOTIFY)
return false;
auto currentEvent = reinterpret_cast<xcb_configure_notify_event_t *>(event);
auto nextEvent = reinterpret_cast<xcb_configure_notify_event_t *>(next);
return currentEvent->event == nextEvent->event;
});
}
return false;
@ -1645,22 +1456,17 @@ void QXcbConnection::processXcbEvents()
exit(1);
}
QXcbEventArray *eventqueue = m_reader->lock();
m_eventQueue->flushBufferedEvents();
for (int i = 0; i < eventqueue->size(); ++i) {
xcb_generic_event_t *event = eventqueue->at(i);
if (!event)
continue;
while (xcb_generic_event_t *event = m_eventQueue->takeFirst()) {
QScopedPointer<xcb_generic_event_t, QScopedPointerPodDeleter> eventGuard(event);
(*eventqueue)[i] = 0;
if (!(event->response_type & ~0x80)) {
handleXcbError(reinterpret_cast<xcb_generic_error_t *>(event));
continue;
}
if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) &&
compressEvent(event, i, eventqueue))
compressEvent(event))
continue;
#ifndef QT_NO_CLIPBOARD
@ -1679,21 +1485,19 @@ void QXcbConnection::processXcbEvents()
m_peekFuncs.erase(std::remove_if(m_peekFuncs.begin(), m_peekFuncs.end(),
isWaitingFor),
m_peekFuncs.end());
m_reader->unlock();
handleXcbEvent(event);
m_reader->lock();
// The lock-based solution used to free the lock inside this loop,
// hence allowing for more events to arrive. ### Check if we want
// this flush here after QTBUG-70095
m_eventQueue->flushBufferedEvents();
}
eventqueue->clear();
m_reader->unlock();
m_peekerIndexCacheDirty = m_mainEventLoopFlushedQueue = true;
// Indicate with a null event that the event the callbacks are waiting for
// is not in the queue currently.
for (PeekFunc f : qAsConst(m_peekFuncs))
f(this, 0);
f(this, nullptr);
m_peekFuncs.clear();
xcb_flush(xcb_connection());

View File

@ -47,15 +47,14 @@
#include "qxcbexport.h"
#include <QHash>
#include <QList>
#include <QMutex>
#include <QObject>
#include <QThread>
#include <QVector>
#include <QVarLengthArray>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/QLoggingCategory>
#include <QtCore/private/qglobal_p.h>
#include "qxcbeventqueue.h"
#include <cstdlib>
#include <memory>
@ -84,6 +83,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaXcb)
Q_DECLARE_LOGGING_CATEGORY(lcQpaPeeker)
Q_DECLARE_LOGGING_CATEGORY(lcQpaKeyboard)
Q_DECLARE_LOGGING_CATEGORY(lcQpaXDnd)
Q_DECLARE_LOGGING_CATEGORY(lcQpaEventReader)
class QXcbVirtualDesktop;
class QXcbScreen;
@ -305,35 +305,6 @@ namespace QXcbAtom {
};
}
typedef QVarLengthArray<xcb_generic_event_t *, 64> QXcbEventArray;
class QXcbConnection;
class QXcbEventReader : public QThread
{
Q_OBJECT
public:
QXcbEventReader(QXcbConnection *connection);
void run() override;
QXcbEventArray *lock();
void unlock();
void start();
void registerEventDispatcher(QAbstractEventDispatcher *dispatcher);
signals:
void eventPending();
private:
void addEvent(xcb_generic_event_t *event);
QMutex m_mutex;
QXcbEventArray m_events;
QXcbConnection *m_connection;
};
class QXcbWindowEventListener
{
public:
@ -375,7 +346,6 @@ private:
QXcbWindow *m_window;
};
class QAbstractEventDispatcher;
class Q_XCB_EXPORT QXcbConnection : public QObject
{
Q_OBJECT
@ -385,6 +355,7 @@ public:
QXcbConnection *connection() const { return const_cast<QXcbConnection *>(this); }
bool isConnected() const;
QXcbEventQueue *eventQueue() const { return m_eventQueue; }
const QList<QXcbVirtualDesktop *> &virtualDesktops() const { return m_virtualDesktops; }
const QList<QXcbScreen *> &screens() const { return m_screens; }
@ -445,21 +416,9 @@ public:
QXcbWindowEventListener *windowEventListenerFromId(xcb_window_t id);
QXcbWindow *platformWindowFromId(xcb_window_t id);
template<typename Functor>
inline xcb_generic_event_t *checkEvent(Functor &&filter, bool removeFromQueue = true);
typedef bool (*PeekFunc)(QXcbConnection *, xcb_generic_event_t *);
void addPeekFunc(PeekFunc f);
// Peek at all queued events
qint32 generatePeekerId();
bool removePeekerId(qint32 peekerId);
enum PeekOption { PeekDefault = 0, PeekFromCachedIndex = 1 }; // see qx11info_x11.h
Q_DECLARE_FLAGS(PeekOptions, PeekOption)
typedef bool (*PeekerCallback)(xcb_generic_event_t *event, void *peekerData);
bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr,
PeekOptions option = PeekDefault, qint32 peekerId = -1);
inline xcb_timestamp_t time() const { return m_time; }
inline void setTime(xcb_timestamp_t t) { if (t > m_time) m_time = t; }
@ -530,24 +489,20 @@ public:
void abortSystemMoveResizeForTouch();
bool isTouchScreen(int id);
#endif
QXcbEventReader *eventReader() const { return m_reader; }
bool canGrab() const { return m_canGrabServer; }
QXcbGlIntegration *glIntegration() const;
void flush() { xcb_flush(m_connection); }
protected:
bool event(QEvent *e) override;
public slots:
void flush() { xcb_flush(m_connection); }
private slots:
void processXcbEvents();
private:
void initializeAllAtoms();
void sendConnectionEvent(QXcbAtom::Atom atom, uint id = 0);
void initializeShm();
void initializeXFixes();
void initializeXRender();
@ -567,7 +522,7 @@ private:
xcb_randr_get_output_info_reply_t *outputInfo);
void destroyScreen(QXcbScreen *screen);
void initializeScreens();
bool compressEvent(xcb_generic_event_t *event, int currentIndex, QXcbEventArray *eventqueue) const;
bool compressEvent(xcb_generic_event_t *event) const;
bool m_xi2Enabled = false;
#if QT_CONFIG(xcb_xinput)
@ -670,7 +625,7 @@ private:
#if QT_CONFIG(xcb_xlib)
void *m_xlib_display = nullptr;
#endif
QXcbEventReader *m_reader = nullptr;
QXcbEventQueue *m_eventQueue = nullptr;
#if QT_CONFIG(xcb_xinput)
QHash<int, TouchDeviceData> m_touchDevices;
@ -722,11 +677,7 @@ private:
xcb_window_t m_qtSelectionOwner = 0;
bool m_mainEventLoopFlushedQueue = false;
qint32 m_peekerIdSource = 0;
bool m_peekerIndexCacheDirty = false;
QHash<qint32, qint32> m_peekerToCachedIndex;
friend class QXcbEventReader;
friend class QXcbEventQueue;
QByteArray m_xdgCurrentDesktop;
};
@ -737,24 +688,6 @@ Q_DECLARE_TYPEINFO(QXcbConnection::TabletData, Q_MOVABLE_TYPE);
#endif
#endif
template<typename Functor>
xcb_generic_event_t *QXcbConnection::checkEvent(Functor &&filter, bool removeFromQueue)
{
QXcbEventArray *eventqueue = m_reader->lock();
for (int i = 0; i < eventqueue->size(); ++i) {
xcb_generic_event_t *event = eventqueue->at(i);
if (event && filter(event, event->response_type & ~0x80)) {
if (removeFromQueue)
(*eventqueue)[i] = nullptr;
m_reader->unlock();
return event;
}
}
m_reader->unlock();
return nullptr;
}
class QXcbConnectionGrabber
{
public:

View File

@ -794,7 +794,7 @@ void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_even
{
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
while (auto nextEvent = connection()->checkEvent(scanner)) {
while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
if (lastEvent != event)
free(lastEvent);
lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
@ -846,7 +846,7 @@ void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
xcb_generic_event_t *nextEvent;
ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
while ((nextEvent = connection()->checkEvent(scanner))) {
while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
if (lastEvent != event)
free(lastEvent);
lastEvent = (xcb_client_message_event_t *)nextEvent;

View File

@ -0,0 +1,332 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qxcbeventqueue.h"
#include "qxcbconnection.h"
#include <QtCore/QObject>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QDebug>
QT_BEGIN_NAMESPACE
/*!
\class QXcbEventQueue
\internal
Lock-free event passing:
The lock-free solution uses a singly-linked list to pass events from the
reader thread to the main thread. An atomic operation is used to sync the
tail node of the list between threads. The reader thread takes special care
when accessing the tail node. It does not dequeue the last node and does not
access (read or write) the tail node's 'next' member. This lets the reader
add more items at the same time as the main thread is dequeuing nodes from
the head. A custom linked list implementation is used, because QLinkedList
does not have any thread-safety guarantees and the custom list is more
lightweight - no reference counting, back links, etc.
Memory management:
In a normally functioning application, XCB plugin won't buffer more than few
batches of events, couple events per batch. Instead of constantly calling
new / delete, we can create a pool of nodes that we reuse. The main thread
uses an atomic operation to sync how many nodes have been restored (available
for reuse). If at some point a user application will block the main thread
for a long time, we might run out of nodes in the pool. Then we create nodes
on a heap. These will be automatically "garbage collected" out of the linked
list, once the main thread stops blocking.
*/
QXcbEventQueue::QXcbEventQueue(QXcbConnection *connection)
: m_connection(connection)
{
connect(this, &QXcbEventQueue::eventsPending, m_connection, &QXcbConnection::processXcbEvents, Qt::QueuedConnection);
connect(this, &QXcbEventQueue::finished, m_connection, &QXcbConnection::processXcbEvents);
// Lets init the list with one node, so we don't have to check for
// this special case in various places.
m_head = m_flushedTail = qXcbEventNodeFactory(nullptr);
m_tail.store(m_head, std::memory_order_release);
start();
}
QXcbEventQueue::~QXcbEventQueue()
{
if (isRunning()) {
sendCloseConnectionEvent();
wait();
}
while (xcb_generic_event_t *event = takeFirst())
free(event);
if (m_head && m_head->fromHeap)
delete m_head; // the deferred node
qCDebug(lcQpaEventReader) << "nodes on heap:" << m_nodesOnHeap;
}
void QXcbEventQueue::registerEventDispatcher(QAbstractEventDispatcher *dispatcher)
{
// Flush the xcb connection before the event dispatcher is going to block.
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_connection, &QXcbConnection::flush);
}
xcb_generic_event_t *QXcbEventQueue::takeFirst()
{
if (isEmpty())
return nullptr;
xcb_generic_event_t *event = nullptr;
do {
event = m_head->event;
if (m_head == m_flushedTail) {
// defer dequeuing until next successful flush of events
if (event) // check if not cleared already by some filter
m_head->event = nullptr; // if not, clear it
} else {
dequeueNode();
if (!event)
continue; // consumed by filter or deferred node
}
} while (!isEmpty() && !event);
m_queueModified = m_peekerIndexCacheDirty = true;
return event;
}
void QXcbEventQueue::dequeueNode()
{
QXcbEventNode *node = m_head;
m_head = m_head->next;
if (node->fromHeap)
delete node;
else
m_nodesRestored.fetch_add(1, std::memory_order_release);
}
void QXcbEventQueue::flushBufferedEvents()
{
m_flushedTail = m_tail.load(std::memory_order_acquire);
}
QXcbEventNode *QXcbEventQueue::qXcbEventNodeFactory(xcb_generic_event_t *event)
{
static QXcbEventNode qXcbNodePool[PoolSize];
if (m_freeNodes == 0) // out of nodes, check if the main thread has released any
m_freeNodes = m_nodesRestored.exchange(0, std::memory_order_acquire);
if (m_freeNodes) {
m_freeNodes--;
if (m_poolIndex == PoolSize) {
// wrap back to the beginning, we always take and restore nodes in-order
m_poolIndex = 0;
}
QXcbEventNode *node = &qXcbNodePool[m_poolIndex++];
node->event = event;
node->next = nullptr;
return node;
}
// the main thread is not flushing events and thus the pool has become empty
auto node = new QXcbEventNode(event);
node->fromHeap = true;
qCDebug(lcQpaEventReader) << "[heap] " << m_nodesOnHeap++;
return node;
}
void QXcbEventQueue::run()
{
xcb_generic_event_t *event = nullptr;
xcb_connection_t *connection = m_connection->xcb_connection();
QXcbEventNode *tail = m_head;
auto enqueueEvent = [&tail, this](xcb_generic_event_t *event) {
if (!isCloseConnectionEvent(event)) {
tail->next = qXcbEventNodeFactory(event);
tail = tail->next;
m_tail.store(tail, std::memory_order_release);
}
};
while (!m_closeConnectionDetected && (event = xcb_wait_for_event(connection))) {
enqueueEvent(event);
while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(connection)))
enqueueEvent(event);
emit eventsPending();
}
}
qint32 QXcbEventQueue::generatePeekerId()
{
const qint32 peekerId = m_peekerIdSource++;
m_peekerToNode.insert(peekerId, nullptr);
return peekerId;
}
bool QXcbEventQueue::removePeekerId(qint32 peekerId)
{
const auto it = m_peekerToNode.find(peekerId);
if (it == m_peekerToNode.constEnd()) {
qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
return false;
}
m_peekerToNode.erase(it);
if (m_peekerToNode.isEmpty()) {
m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
m_peekerIndexCacheDirty = false;
}
return true;
}
bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
PeekOptions option, qint32 peekerId)
{
const bool peekerIdProvided = peekerId != -1;
auto peekerToNodeIt = m_peekerToNode.find(peekerId);
if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.constEnd()) {
qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
return false;
}
const bool useCache = option.testFlag(PeekOption::PeekFromCachedIndex);
if (useCache && !peekerIdProvided) {
qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
return false;
}
if (peekerIdProvided && m_peekerIndexCacheDirty) {
for (auto &node : m_peekerToNode) // reset cache
node = nullptr;
m_peekerIndexCacheDirty = false;
}
flushBufferedEvents();
if (isEmpty())
return false;
const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
if (useCache) {
const QXcbEventNode *cachedNode = peekerToNodeIt.value();
if (!cachedNode)
return m_head; // cache was reset
if (cachedNode == m_flushedTail)
return nullptr; // no new events since the last call
return cachedNode->next;
}
return m_head;
}();
if (!startNode)
return false;
// A peeker may call QCoreApplication::processEvents(), which will cause
// QXcbConnection::processXcbEvents() to modify the queue we are currently
// looping through;
m_queueModified = false;
bool result = false;
QXcbEventNode *node = startNode;
do {
xcb_generic_event_t *event = node->event;
if (event && peeker(event, peekerData)) {
result = true;
break;
}
if (node == m_flushedTail)
break;
node = node->next;
} while (!m_queueModified);
// Update the cached index if the queue was not modified, and hence the
// cache is still valid.
if (peekerIdProvided && node != startNode && !m_queueModified) {
// Before updating, make sure that a peeker callback did not remove
// the peeker id.
peekerToNodeIt = m_peekerToNode.find(peekerId);
if (peekerToNodeIt != m_peekerToNode.constEnd())
*peekerToNodeIt = node; // id still in the cache, update node
}
return result;
}
void QXcbEventQueue::sendCloseConnectionEvent() const
{
// A hack to close XCB connection. Apparently XCB does not have any APIs for this?
xcb_client_message_event_t event;
memset(&event, 0, sizeof(event));
xcb_connection_t *c = m_connection->xcb_connection();
const xcb_window_t window = xcb_generate_id(c);
xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_connection->m_setup);
xcb_screen_t *screen = it.data;
xcb_create_window(c, XCB_COPY_FROM_PARENT,
window, screen->root,
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
screen->root_visual, 0, nullptr);
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.sequence = 0;
event.window = window;
event.type = m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION);
event.data.data32[0] = 0;
xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
xcb_destroy_window(c, window);
xcb_flush(c);
}
bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
{
if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
if (clientMessage->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
m_closeConnectionDetected = true;
}
return m_closeConnectionDetected;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,160 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QXCBEVENTQUEUE_H
#define QXCBEVENTQUEUE_H
#include <QtCore/QThread>
#include <QtCore/QHash>
#include <xcb/xcb.h>
#include <atomic>
QT_BEGIN_NAMESPACE
struct QXcbEventNode {
QXcbEventNode(xcb_generic_event_t *e = nullptr)
: event(e) { }
xcb_generic_event_t *event;
QXcbEventNode *next = nullptr;
bool fromHeap = false;
};
class QXcbConnection;
class QAbstractEventDispatcher;
class QXcbEventQueue : public QThread
{
Q_OBJECT
public:
QXcbEventQueue(QXcbConnection *connection);
~QXcbEventQueue();
enum { PoolSize = 100 }; // 2.4 kB with 100 nodes
enum PeekOption {
PeekDefault = 0, // see qx11info_x11.h for docs
PeekFromCachedIndex = 1,
PeekRetainMatch = 2,
PeekRemoveMatch = 3,
PeekRemoveMatchContinue = 4
};
Q_DECLARE_FLAGS(PeekOptions, PeekOption)
void run() override;
void registerEventDispatcher(QAbstractEventDispatcher *dispatcher);
bool isEmpty() const { return m_head == m_flushedTail && !m_head->event; }
xcb_generic_event_t *takeFirst();
void flushBufferedEvents();
// ### peek() and peekEventQueue() could be unified. Note that peekEventQueue()
// is public API exposed via QX11Extras/QX11Info.
template<typename Peeker>
xcb_generic_event_t *peek(Peeker &&peeker) {
return peek(PeekRemoveMatch, std::forward<Peeker>(peeker));
}
template<typename Peeker>
inline xcb_generic_event_t *peek(PeekOption config, Peeker &&peeker);
qint32 generatePeekerId();
bool removePeekerId(qint32 peekerId);
using PeekerCallback = bool (*)(xcb_generic_event_t *event, void *peekerData);
bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr,
PeekOptions option = PeekDefault, qint32 peekerId = -1);
signals:
void eventsPending();
private:
QXcbEventNode *qXcbEventNodeFactory(xcb_generic_event_t *event);
void dequeueNode();
void sendCloseConnectionEvent() const;
bool isCloseConnectionEvent(const xcb_generic_event_t *event);
QXcbEventNode *m_head = nullptr;
QXcbEventNode *m_flushedTail = nullptr;
std::atomic<QXcbEventNode *> m_tail { nullptr };
std::atomic_uint m_nodesRestored { 0 };
QXcbConnection *m_connection = nullptr;
bool m_closeConnectionDetected = false;
uint m_freeNodes = PoolSize;
uint m_poolIndex = 0;
qint32 m_peekerIdSource = 0;
bool m_queueModified = false;
bool m_peekerIndexCacheDirty = false;
QHash<qint32, QXcbEventNode *> m_peekerToNode;
// debug stats
quint64 m_nodesOnHeap = 0;
};
template<typename Peeker>
xcb_generic_event_t *QXcbEventQueue::peek(PeekOption option, Peeker &&peeker)
{
flushBufferedEvents();
if (isEmpty())
return nullptr;
QXcbEventNode *node = m_head;
do {
xcb_generic_event_t *event = node->event;
if (event && peeker(event, event->response_type & ~0x80)) {
if (option == PeekRemoveMatch || option == PeekRemoveMatchContinue)
node->event = nullptr;
if (option != PeekRemoveMatchContinue)
return event;
}
if (node == m_flushedTail)
break;
node = node->next;
} while (true);
return nullptr;
}
QT_END_NAMESPACE
#endif

View File

@ -46,6 +46,7 @@
#include "qxcbbackingstore.h"
#include "qxcbnativeinterface.h"
#include "qxcbclipboard.h"
#include "qxcbeventqueue.h"
#if QT_CONFIG(draganddrop)
#include "qxcbdrag.h"
#endif
@ -344,7 +345,7 @@ QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
{
QAbstractEventDispatcher *dispatcher = createUnixEventDispatcher();
for (int i = 0; i < m_connections.size(); i++)
m_connections[i]->eventReader()->registerEventDispatcher(dispatcher);
m_connections[i]->eventQueue()->registerEventDispatcher(dispatcher);
return dispatcher;
}

View File

@ -1541,7 +1541,8 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
} else {
m_isAutoRepeat = false;
// Look at the next event in the queue to see if we are auto-repeating.
connection()->checkEvent([this, time, code](xcb_generic_event_t *event, int type) {
connection()->eventQueue()->peek(QXcbEventQueue::PeekRetainMatch,
[this, time, code](xcb_generic_event_t *event, int type) {
if (type == XCB_KEY_PRESS) {
auto keyPress = reinterpret_cast<xcb_key_press_event_t *>(event);
m_isAutoRepeat = keyPress->time == time && keyPress->detail == code;
@ -1549,7 +1550,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
m_autoRepeatCode = code;
}
return true;
}, false /* removeFromQueue */);
});
}
bool filtered = false;

View File

@ -437,20 +437,20 @@ void QXcbNativeInterface::setAppUserTime(QScreen* screen, xcb_timestamp_t time)
qint32 QXcbNativeInterface::generatePeekerId()
{
QXcbIntegration *integration = QXcbIntegration::instance();
return integration->defaultConnection()->generatePeekerId();
return integration->defaultConnection()->eventQueue()->generatePeekerId();
}
bool QXcbNativeInterface::removePeekerId(qint32 peekerId)
{
QXcbIntegration *integration = QXcbIntegration::instance();
return integration->defaultConnection()->removePeekerId(peekerId);
return integration->defaultConnection()->eventQueue()->removePeekerId(peekerId);
}
bool QXcbNativeInterface::peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData,
QXcbConnection::PeekOptions option, qint32 peekerId)
bool QXcbNativeInterface::peekEventQueue(QXcbEventQueue::PeekerCallback peeker, void *peekerData,
QXcbEventQueue::PeekOptions option, qint32 peekerId)
{
QXcbIntegration *integration = QXcbIntegration::instance();
return integration->defaultConnection()->peekEventQueue(peeker, peekerData, option, peekerId);
return integration->defaultConnection()->eventQueue()->peekEventQueue(peeker, peekerData, option, peekerId);
}
void QXcbNativeInterface::setStartupId(const char *data)

View File

@ -118,8 +118,8 @@ public:
static qint32 generatePeekerId();
static bool removePeekerId(qint32 peekerId);
static bool peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData = nullptr,
QXcbConnection::PeekOptions option = QXcbConnection::PeekDefault,
static bool peekEventQueue(QXcbEventQueue::PeekerCallback peeker, void *peekerData = nullptr,
QXcbEventQueue::PeekOptions option = QXcbEventQueue::PeekDefault,
qint32 peekerId = -1);
Q_INVOKABLE QString dumpConnectionNativeWindows(const QXcbConnection *connection, WId root) const;

View File

@ -1818,21 +1818,20 @@ void QXcbWindow::handleExposeEvent(const xcb_expose_event_t *event)
m_exposeRegion |= rect;
bool pending = true;
xcb_generic_event_t *e = nullptr;
do { // compress expose events
e = connection()->checkEvent([this, &pending](xcb_generic_event_t *event, int type) {
if (type != XCB_EXPOSE)
return false;
auto expose = reinterpret_cast<xcb_expose_event_t *>(event);
if (expose->window != m_window)
return false;
if (expose->count == 0)
pending = false;
m_exposeRegion |= QRect(expose->x, expose->y, expose->width, expose->height);
return true;
});
free(e);
} while (e);
connection()->eventQueue()->peek(QXcbEventQueue::PeekRemoveMatchContinue,
[this, &pending](xcb_generic_event_t *event, int type) {
if (type != XCB_EXPOSE)
return false;
auto expose = reinterpret_cast<xcb_expose_event_t *>(event);
if (expose->window != m_window)
return false;
if (expose->count == 0)
pending = false;
m_exposeRegion |= QRect(expose->x, expose->y, expose->width, expose->height);
free(expose);
return true;
});
// if count is non-zero there are more expose events pending
if (event->count == 0 || !pending) {
@ -2152,13 +2151,11 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
return;
// check if enter event is buffered
auto event = connection()->checkEvent([](xcb_generic_event_t *event, int type) {
auto event = connection()->eventQueue()->peek([](xcb_generic_event_t *event, int type) {
if (type != XCB_ENTER_NOTIFY)
return false;
auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event);
if (ignoreEnterEvent(enter->mode, enter->detail))
return false;
return true;
return !ignoreEnterEvent(enter->mode, enter->detail);
});
auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event);
QXcbWindow *enterWindow = enter ? connection()->platformWindowFromId(enter->event) : nullptr;

View File

@ -27,7 +27,8 @@ SOURCES = \
qxcbcursor.cpp \
qxcbimage.cpp \
qxcbxsettings.cpp \
qxcbsystemtraytracker.cpp
qxcbsystemtraytracker.cpp \
qxcbeventqueue.cpp
HEADERS = \
qxcbclipboard.h \
@ -45,7 +46,8 @@ HEADERS = \
qxcbimage.h \
qxcbxsettings.h \
qxcbsystemtraytracker.h \
qxcbxkbcommon.h
qxcbxkbcommon.h \
qxcbeventqueue.h
qtConfig(draganddrop) {
SOURCES += qxcbdrag.cpp