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
qcfsocketnotifier.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qsocketnotifier.h>
8#include <QtCore/qthread.h>
9
11
12/**************************************************************************
13 Socket Notifiers
14 *************************************************************************/
15void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,
16 const void *data, void *info)
17{
18
19 QCFSocketNotifier *cfSocketNotifier = static_cast<QCFSocketNotifier *>(info);
20 int nativeSocket = CFSocketGetNative(s);
21 MacSocketInfo *socketInfo = cfSocketNotifier->macSockets.value(nativeSocket);
22 QEvent notifierEvent(QEvent::SockAct);
23
24 // There is a race condition that happen where we disable the notifier and
25 // the kernel still has a notification to pass on. We then get this
26 // notification after we've successfully disabled the CFSocket, but our Qt
27 // notifier is now gone. The upshot is we have to check the notifier
28 // every time.
29 if (callbackType == kCFSocketConnectCallBack) {
30 // The data pointer will be non-null on connection error
31 if (data) {
32 if (socketInfo->readNotifier)
33 QCoreApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
34 if (socketInfo->writeNotifier)
35 QCoreApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
36 }
37 } else if (callbackType == kCFSocketReadCallBack) {
38 if (socketInfo->readNotifier && socketInfo->readEnabled) {
39 socketInfo->readEnabled = false;
40 QCoreApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
41 }
42 } else if (callbackType == kCFSocketWriteCallBack) {
43 if (socketInfo->writeNotifier && socketInfo->writeEnabled) {
44 socketInfo->writeEnabled = false;
45 QCoreApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
46 }
47 }
48
49 if (cfSocketNotifier->maybeCancelWaitForMoreEvents)
50 cfSocketNotifier->maybeCancelWaitForMoreEvents(cfSocketNotifier->eventDispatcher);
51}
52
53/*
54 Adds a loop source for the given socket to the current run loop.
55*/
57{
58 CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
59 if (!loopSource)
60 return 0;
61
62 CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, kCFRunLoopCommonModes);
63 return loopSource;
64}
65
66/*
67 Removes the loop source for the given socket from the current run loop.
68*/
69void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
70{
71 Q_ASSERT(runloop);
72 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop, kCFRunLoopCommonModes);
73 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
74 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
75}
76
77QCFSocketNotifier::QCFSocketNotifier()
78 : eventDispatcher(0)
79 , maybeCancelWaitForMoreEvents(0)
80 , enableNotifiersObserver(0)
81{
82
83}
84
85QCFSocketNotifier::~QCFSocketNotifier()
86{
87
88}
89
90void QCFSocketNotifier::setHostEventDispatcher(QAbstractEventDispatcher *hostEventDispacher)
91{
92 eventDispatcher = hostEventDispacher;
93}
94
95void QCFSocketNotifier::setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack)
96{
97 maybeCancelWaitForMoreEvents = callBack;
98}
99
100void QCFSocketNotifier::registerSocketNotifier(QSocketNotifier *notifier)
101{
102 Q_ASSERT(notifier);
103 int nativeSocket = notifier->socket();
104 int type = notifier->type();
105#ifndef QT_NO_DEBUG
106 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
107 qWarning("QSocketNotifier: Internal error");
108 return;
109 } else if (notifier->thread() != eventDispatcher->thread()
110 || eventDispatcher->thread() != QThread::currentThread()) {
111 qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
112 return;
113 }
114#endif
115
116 if (type == QSocketNotifier::Exception) {
117 qWarning("QSocketNotifier::Exception is not supported on iOS");
118 return;
119 }
120
121 // Check if we have a CFSocket for the native socket, create one if not.
122 MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
123 if (!socketInfo) {
124 socketInfo = new MacSocketInfo();
125
126 // Create CFSocket, specify that we want both read and write callbacks (the callbacks
127 // are enabled/disabled later on).
128 const int callbackTypes = kCFSocketConnectCallBack | kCFSocketReadCallBack | kCFSocketWriteCallBack;
129 CFSocketContext context = {0, this, 0, 0, 0};
130 socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);
131 if (CFSocketIsValid(socketInfo->socket) == false) {
132 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");
133 return;
134 }
135
136 CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket);
137 // QSocketNotifier doesn't close the socket upon destruction/invalidation
138 flags &= ~kCFSocketCloseOnInvalidate;
139 // Explicitly disable automatic re-enable, as we do that manually on each runloop pass
140 flags &= ~(kCFSocketAutomaticallyReenableWriteCallBack | kCFSocketAutomaticallyReenableReadCallBack);
141 CFSocketSetSocketFlags(socketInfo->socket, flags);
142
143 macSockets.insert(nativeSocket, socketInfo);
144 }
145
146 if (type == QSocketNotifier::Read) {
147 Q_ASSERT(socketInfo->readNotifier == 0);
148 socketInfo->readNotifier = notifier;
149 socketInfo->readEnabled = false;
150 } else if (type == QSocketNotifier::Write) {
151 Q_ASSERT(socketInfo->writeNotifier == 0);
152 socketInfo->writeNotifier = notifier;
153 socketInfo->writeEnabled = false;
154 }
155
156 if (!enableNotifiersObserver) {
157 // Create a run loop observer which enables the socket notifiers on each
158 // pass of the run loop, before any sources are processed.
159 CFRunLoopObserverContext context = {};
160 context.info = this;
161 enableNotifiersObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeSources,
162 true, 0, enableSocketNotifiers, &context);
163 Q_ASSERT(enableNotifiersObserver);
164 CFRunLoopAddObserver(CFRunLoopGetCurrent(), enableNotifiersObserver, kCFRunLoopCommonModes);
165 }
166}
167
168void QCFSocketNotifier::unregisterSocketNotifier(QSocketNotifier *notifier)
169{
170 Q_ASSERT(notifier);
171 int nativeSocket = notifier->socket();
172 int type = notifier->type();
173#ifndef QT_NO_DEBUG
174 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
175 qWarning("QSocketNotifier: Internal error");
176 return;
177 } else if (notifier->thread() != eventDispatcher->thread() || eventDispatcher->thread() != QThread::currentThread()) {
178 qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");
179 return;
180 }
181#endif
182
183 if (type == QSocketNotifier::Exception) {
184 qWarning("QSocketNotifier::Exception is not supported on iOS");
185 return;
186 }
187 MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
188 if (!socketInfo) {
189 qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");
190 return;
191 }
192
193 // Decrement read/write counters and disable callbacks if necessary.
194 if (type == QSocketNotifier::Read) {
195 Q_ASSERT(notifier == socketInfo->readNotifier);
196 socketInfo->readNotifier = 0;
197 socketInfo->readEnabled = false;
198 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
199 } else if (type == QSocketNotifier::Write) {
200 Q_ASSERT(notifier == socketInfo->writeNotifier);
201 socketInfo->writeNotifier = 0;
202 socketInfo->writeEnabled = false;
203 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
204 }
205
206 // Remove CFSocket from runloop if this was the last QSocketNotifier.
207 if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) {
208 unregisterSocketInfo(socketInfo);
209 delete socketInfo;
210 macSockets.remove(nativeSocket);
211 }
212}
213
214void QCFSocketNotifier::removeSocketNotifiers()
215{
216 // Remove CFSockets from the runloop.
217 for (MacSocketInfo *socketInfo : std::as_const(macSockets)) {
218 unregisterSocketInfo(socketInfo);
219 delete socketInfo;
220 }
221
222 macSockets.clear();
223
224 destroyRunLoopObserver();
225}
226
227void QCFSocketNotifier::destroyRunLoopObserver()
228{
229 if (!enableNotifiersObserver)
230 return;
231
232 CFRunLoopObserverInvalidate(enableNotifiersObserver);
233 CFRelease(enableNotifiersObserver);
234 enableNotifiersObserver = 0;
235}
236
237void QCFSocketNotifier::unregisterSocketInfo(MacSocketInfo *socketInfo)
238{
239 if (socketInfo->runloop) {
240 if (CFSocketIsValid(socketInfo->socket))
241 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
242 CFRunLoopSourceInvalidate(socketInfo->runloop);
243 CFRelease(socketInfo->runloop);
244 }
245 CFSocketInvalidate(socketInfo->socket);
246 CFRelease(socketInfo->socket);
247}
248
249void QCFSocketNotifier::enableSocketNotifiers(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info)
250{
251 Q_UNUSED(ref);
252 Q_UNUSED(activity);
253
254 const QCFSocketNotifier *that = static_cast<QCFSocketNotifier *>(info);
255
256 for (MacSocketInfo *socketInfo : that->macSockets) {
257 if (!CFSocketIsValid(socketInfo->socket))
258 continue;
259
260 if (!socketInfo->runloop) {
261 // Add CFSocket to runloop.
262 if (!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) {
263 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");
264 CFSocketInvalidate(socketInfo->socket);
265 continue;
266 }
267
268 // Apple docs say: "If a callback is automatically re-enabled,
269 // it is called every time the condition becomes true ... If a
270 // callback is not automatically re-enabled, then it gets called
271 // exactly once, and is not called again until you manually
272 // re-enable that callback by calling CFSocketEnableCallBacks()".
273 // So, we don't need to enable callbacks on registering.
274 socketInfo->readEnabled = (socketInfo->readNotifier != nullptr);
275 if (!socketInfo->readEnabled)
276 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
277 socketInfo->writeEnabled = (socketInfo->writeNotifier != nullptr);
278 if (!socketInfo->writeEnabled)
279 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
280 continue;
281 }
282
283 if (socketInfo->readNotifier && !socketInfo->readEnabled) {
284 socketInfo->readEnabled = true;
285 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
286 }
287 if (socketInfo->writeNotifier && !socketInfo->writeEnabled) {
288 socketInfo->writeEnabled = true;
289 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
290 }
291 }
292}
293
294QT_END_NAMESPACE
Combined button and popup list for selecting options.
CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket)
void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
QT_BEGIN_NAMESPACE void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef, const void *data, void *info)