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