Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qxcbeventqueue.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtCore/QObject>
9#include <QtCore/QCoreApplication>
10#include <QtCore/QAbstractEventDispatcher>
11#include <QtCore/QMutex>
12#include <QtCore/QDebug>
13
15
16Q_CONSTINIT static QBasicMutex qAppExiting;
18
19/*!
20 \class QXcbEventQueue
21 \internal
22
23 Lock-free event passing:
24
25 The lock-free solution uses a singly-linked list to pass events from the
26 reader thread to the main thread. An atomic operation is used to sync the
27 tail node of the list between threads. The reader thread takes special care
28 when accessing the tail node. It does not dequeue the last node and does not
29 access (read or write) the tail node's 'next' member. This lets the reader
30 add more items at the same time as the main thread is dequeuing nodes from
31 the head. A custom linked list implementation is used, because std::list
32 does not have any thread-safety guarantees. The custom list is
33 lightweight - no reference counting, back links, etc.
34
35 Memory management:
36
37 In a normally functioning application, XCB plugin won't buffer more than few
38 batches of events, couple events per batch. Instead of constantly calling
39 new / delete, we can create a pool of nodes that we reuse. The main thread
40 uses an atomic operation to sync how many nodes have been restored (available
41 for reuse). If at some point a user application will block the main thread
42 for a long time, we might run out of nodes in the pool. Then we create nodes
43 on a heap. These will be automatically "garbage collected" out of the linked
44 list, once the main thread stops blocking.
45*/
46
47QXcbEventQueue::QXcbEventQueue(QXcbConnection *connection)
48 : m_connection(connection)
49{
50 // When running test cases in auto tests, static variables are preserved
51 // between test function runs, even if Q*Application object is destroyed.
52 // Reset to default value to account for this.
54 qAddPostRoutine([]() {
55 QMutexLocker locker(&qAppExiting);
57 });
58
59 // Lets init the list with one node, so we don't have to check for
60 // this special case in various places.
61 m_head = m_flushedTail = qXcbEventNodeFactory(nullptr);
62 m_tail.store(m_head, std::memory_order_release);
63
64 start();
65}
66
68{
69 if (isRunning()) {
70 sendCloseConnectionEvent();
71 wait();
72 }
73
75 while (xcb_generic_event_t *event = takeFirst(QEventLoop::AllEvents))
76 free(event);
77
78 if (m_head && m_head->fromHeap)
79 delete m_head; // the deferred node
80
81 qCDebug(lcQpaEventReader) << "nodes on heap:" << m_nodesOnHeap;
82}
83
84xcb_generic_event_t *QXcbEventQueue::takeFirst(QEventLoop::ProcessEventsFlags flags)
85{
86 // This is the level at which we were moving excluded user input events into
87 // separate queue in Qt 4 (see qeventdispatcher_x11.cpp). In this case
88 // QXcbEventQueue represents Xlib's internal event queue. In Qt 4, Xlib's
89 // event queue peeking APIs would not see these events anymore, the same way
90 // our peeking functions do not consider m_inputEvents. This design is
91 // intentional to keep the same behavior. We could do filtering directly on
92 // QXcbEventQueue, without the m_inputEvents, but it is not clear if it is
93 // needed by anyone who peeks at the native event queue.
94
95 bool excludeUserInputEvents = flags.testFlag(QEventLoop::ExcludeUserInputEvents);
96 if (excludeUserInputEvents) {
97 xcb_generic_event_t *event = nullptr;
98 while ((event = takeFirst())) {
99 if (m_connection->isUserInputEvent(event)) {
100 m_inputEvents << event;
101 continue;
102 }
103 break;
104 }
105 return event;
106 }
107
108 if (!m_inputEvents.isEmpty())
109 return m_inputEvents.takeFirst();
110 return takeFirst();
111}
112
113xcb_generic_event_t *QXcbEventQueue::takeFirst()
114{
115 if (isEmpty())
116 return nullptr;
117
118 xcb_generic_event_t *event = nullptr;
119 do {
120 event = m_head->event;
121 if (m_head == m_flushedTail) {
122 // defer dequeuing until next successful flush of events
123 if (event) // check if not cleared already by some filter
124 m_head->event = nullptr; // if not, clear it
125 } else {
126 dequeueNode();
127 if (!event)
128 continue; // consumed by filter or deferred node
129 }
130 } while (!isEmpty() && !event);
131
132 m_queueModified = m_peekerIndexCacheDirty = true;
133
134 return event;
135}
136
137void QXcbEventQueue::dequeueNode()
138{
139 QXcbEventNode *node = m_head;
140 m_head = m_head->next;
141 if (node->fromHeap)
142 delete node;
143 else
144 m_nodesRestored.fetch_add(1, std::memory_order_release);
145}
146
148{
149 m_flushedTail = m_tail.load(std::memory_order_acquire);
150}
151
152QXcbEventNode *QXcbEventQueue::qXcbEventNodeFactory(xcb_generic_event_t *event)
153{
154 static QXcbEventNode qXcbNodePool[PoolSize];
155
156 if (m_freeNodes == 0) // out of nodes, check if the main thread has released any
157 m_freeNodes = m_nodesRestored.exchange(0, std::memory_order_acquire);
158
159 if (m_freeNodes) {
160 m_freeNodes--;
161 if (m_poolIndex == PoolSize) {
162 // wrap back to the beginning, we always take and restore nodes in-order
163 m_poolIndex = 0;
164 }
165 QXcbEventNode *node = &qXcbNodePool[m_poolIndex++];
166 node->event = event;
167 node->next = nullptr;
168 return node;
169 }
170
171 // the main thread is not flushing events and thus the pool has become empty
172 auto node = new QXcbEventNode(event);
173 node->fromHeap = true;
174 qCDebug(lcQpaEventReader) << "[heap] " << m_nodesOnHeap++;
175 return node;
176}
177
178void QXcbEventQueue::run()
179{
180 xcb_generic_event_t *event = nullptr;
181 xcb_connection_t *connection = m_connection->xcb_connection();
182 QXcbEventNode *tail = m_head;
183
184 auto enqueueEvent = [&tail, this](xcb_generic_event_t *event) {
185 if (!isCloseConnectionEvent(event)) {
186 tail->next = qXcbEventNodeFactory(event);
187 tail = tail->next;
188 m_tail.store(tail, std::memory_order_release);
189 } else {
190 free(event);
191 }
192 };
193
194 while (!m_closeConnectionDetected && (event = xcb_wait_for_event(connection))) {
195 // This lock can block only if there are users of waitForNewEvents().
196 // Currently only the clipboard implementation relies on it.
197 m_newEventsMutex.lock();
198 enqueueEvent(event);
199 while (!m_closeConnectionDetected && (event = xcb_poll_for_queued_event(connection)))
200 enqueueEvent(event);
201
202 m_newEventsCondition.wakeOne();
203 m_newEventsMutex.unlock();
205 }
206
207 if (!m_closeConnectionDetected) {
208 // Connection was terminated not by us. Wake up dispatcher, which will
209 // call processXcbEvents(), where we handle the connection errors via
210 // xcb_connection_has_error().
212 }
213}
214
216{
217 QMutexLocker locker(&qAppExiting);
218 if (!dispatcherOwnerDestructing) {
219 // This thread can run before a dispatcher has been created,
220 // so check if it is ready.
221 if (QCoreApplication::eventDispatcher())
222 QCoreApplication::eventDispatcher()->wakeUp();
223 }
224}
225
227{
228 const qint32 peekerId = m_peekerIdSource++;
229 m_peekerToNode.insert(peekerId, nullptr);
230 return peekerId;
231}
232
233bool QXcbEventQueue::removePeekerId(qint32 peekerId)
234{
235 const auto it = m_peekerToNode.constFind(peekerId);
236 if (it == m_peekerToNode.constEnd()) {
237 qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId);
238 return false;
239 }
240 m_peekerToNode.erase(it);
241 if (m_peekerToNode.isEmpty()) {
242 m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs
243 m_peekerIndexCacheDirty = false;
244 }
245 return true;
246}
247
248bool QXcbEventQueue::peekEventQueue(PeekerCallback peeker, void *peekerData,
249 PeekOptions option, qint32 peekerId)
250{
251 const bool peekerIdProvided = peekerId != -1;
252 auto peekerToNodeIt = m_peekerToNode.find(peekerId);
253
254 if (peekerIdProvided && peekerToNodeIt == m_peekerToNode.end()) {
255 qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId);
256 return false;
257 }
258
259 const bool useCache = option.testFlag(PeekOption::PeekFromCachedIndex);
260 if (useCache && !peekerIdProvided) {
261 qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id");
262 return false;
263 }
264
265 if (peekerIdProvided && m_peekerIndexCacheDirty) {
266 for (auto &node : m_peekerToNode) // reset cache
267 node = nullptr;
268 m_peekerIndexCacheDirty = false;
269 }
270
272 if (isEmpty())
273 return false;
274
275 const auto startNode = [this, useCache, peekerToNodeIt]() -> QXcbEventNode * {
276 if (useCache) {
277 const QXcbEventNode *cachedNode = peekerToNodeIt.value();
278 if (!cachedNode)
279 return m_head; // cache was reset
280 if (cachedNode == m_flushedTail)
281 return nullptr; // no new events since the last call
282 return cachedNode->next;
283 }
284 return m_head;
285 }();
286
287 if (!startNode)
288 return false;
289
290 // A peeker may call QCoreApplication::processEvents(), which will cause
291 // QXcbConnection::processXcbEvents() to modify the queue we are currently
292 // looping through;
293 m_queueModified = false;
294 bool result = false;
295
296 QXcbEventNode *node = startNode;
297 do {
298 xcb_generic_event_t *event = node->event;
299 if (event && peeker(event, peekerData)) {
300 result = true;
301 break;
302 }
303 if (node == m_flushedTail)
304 break;
305 node = node->next;
306 } while (!m_queueModified);
307
308 // Update the cached index if the queue was not modified, and hence the
309 // cache is still valid.
310 if (peekerIdProvided && node != startNode && !m_queueModified) {
311 // Before updating, make sure that a peeker callback did not remove
312 // the peeker id.
313 peekerToNodeIt = m_peekerToNode.find(peekerId);
314 if (peekerToNodeIt != m_peekerToNode.end())
315 *peekerToNodeIt = node; // id still in the cache, update node
316 }
317
318 return result;
319}
320
321void QXcbEventQueue::waitForNewEvents(const QXcbEventNode *sinceFlushedTail,
322 unsigned long time)
323{
324 QMutexLocker locker(&m_newEventsMutex);
326 if (sinceFlushedTail != m_flushedTail)
327 return;
328 m_newEventsCondition.wait(&m_newEventsMutex, time);
329}
330
331void QXcbEventQueue::sendCloseConnectionEvent() const
332{
333 // A hack to close XCB connection. Apparently XCB does not have any APIs for this?
334 xcb_client_message_event_t event;
335 memset(&event, 0, sizeof(event));
336
337 xcb_connection_t *c = m_connection->xcb_connection();
338 const xcb_window_t window = xcb_generate_id(c);
339 xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_connection->setup());
340 xcb_screen_t *screen = it.data;
341 xcb_create_window(c, XCB_COPY_FROM_PARENT,
342 window, screen->root,
343 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
344 screen->root_visual, 0, nullptr);
345
346 event.response_type = XCB_CLIENT_MESSAGE;
347 event.format = 32;
348 event.sequence = 0;
349 event.window = window;
350 event.type = m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION);
351 event.data.data32[0] = 0;
352
353 xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
354 xcb_destroy_window(c, window);
355 xcb_flush(c);
356}
357
358bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
359{
360 if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
361 auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
362 if (clientMessage->type == m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION))
363 m_closeConnectionDetected = true;
364 }
365 return m_closeConnectionDetected;
366}
367
368QT_END_NAMESPACE
369
370#include "moc_qxcbeventqueue.cpp"
@ Atom_QT_CLOSE_CONNECTION
Definition qxcbatom.h:57
bool(*)(xcb_generic_event_t *event, void *peekerData) PeekerCallback
bool removePeekerId(qint32 peekerId)
qint32 generatePeekerId()
bool isEmpty() const
xcb_generic_event_t * takeFirst(QEventLoop::ProcessEventsFlags flags)
xcb_generic_event_t * takeFirst()
void waitForNewEvents(const QXcbEventNode *sinceFlushedTail, unsigned long time=(std::numeric_limits< unsigned long >::max)())
bool peekEventQueue(PeekerCallback peeker, void *peekerData=nullptr, PeekOptions option=PeekDefault, qint32 peekerId=-1)
static Q_CONSTINIT bool dispatcherOwnerDestructing