summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qcfsocketnotifier.cpp
blob: 21a22a7439c09adf0981ba5756fb9b1a36b3f392 (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
// Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qcfsocketnotifier_p.h"#include <QtCore/qcoreapplication.h>#include <QtCore/qsocketnotifier.h>#include <QtCore/qthread.h> QT_BEGIN_NAMESPACE /************************************************************************** Socket Notifiers *************************************************************************/voidqt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,const void*data,void*info){ QCFSocketNotifier *cfSocketNotifier =static_cast<QCFSocketNotifier *>(info);int nativeSocket =CFSocketGetNative(s); MacSocketInfo *socketInfo = cfSocketNotifier->macSockets.value(nativeSocket); QEvent notifierEvent(QEvent::SockAct);// There is a race condition that happen where we disable the notifier and// the kernel still has a notification to pass on. We then get this// notification after we've successfully disabled the CFSocket, but our Qt// notifier is now gone. The upshot is we have to check the notifier// every time.if(callbackType == kCFSocketConnectCallBack) {// The data pointer will be non-null on connection errorif(data) {if(socketInfo->readNotifier)QCoreApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);if(socketInfo->writeNotifier)QCoreApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);}}else if(callbackType == kCFSocketReadCallBack) {if(socketInfo->readNotifier && socketInfo->readEnabled) { socketInfo->readEnabled =false;QCoreApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);}}else if(callbackType == kCFSocketWriteCallBack) {if(socketInfo->writeNotifier && socketInfo->writeEnabled) { socketInfo->writeEnabled =false;QCoreApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);}}if(cfSocketNotifier->maybeCancelWaitForMoreEvents) cfSocketNotifier->maybeCancelWaitForMoreEvents(cfSocketNotifier->eventDispatcher);}/* Adds a loop source for the given socket to the current run loop.*/ CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket){ CFRunLoopSourceRef loopSource =CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket,0);if(!loopSource)return0;CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, kCFRunLoopCommonModes);return loopSource;}/* Removes the loop source for the given socket from the current run loop.*/voidqt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop){Q_ASSERT(runloop);CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop, kCFRunLoopCommonModes);CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);}QCFSocketNotifier::QCFSocketNotifier():eventDispatcher(0),maybeCancelWaitForMoreEvents(0),enableNotifiersObserver(0){}QCFSocketNotifier::~QCFSocketNotifier(){}voidQCFSocketNotifier::setHostEventDispatcher(QAbstractEventDispatcher *hostEventDispacher){ eventDispatcher = hostEventDispacher;}voidQCFSocketNotifier::setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack){ maybeCancelWaitForMoreEvents = callBack;}voidQCFSocketNotifier::registerSocketNotifier(QSocketNotifier *notifier){Q_ASSERT(notifier);int nativeSocket = notifier->socket();int type = notifier->type();#ifndef QT_NO_DEBUGif(nativeSocket <0|| nativeSocket > FD_SETSIZE) {qWarning("QSocketNotifier: Internal error");return;}else if(notifier->thread() != eventDispatcher->thread()|| eventDispatcher->thread() !=QThread::currentThread()) {qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");return;}#endifif(type ==QSocketNotifier::Exception) {qWarning("QSocketNotifier::Exception is not supported on iOS");return;}// Check if we have a CFSocket for the native socket, create one if not. MacSocketInfo *socketInfo = macSockets.value(nativeSocket);if(!socketInfo) { socketInfo =newMacSocketInfo();// Create CFSocket, specify that we want both read and write callbacks (the callbacks// are enabled/disabled later on).const int callbackTypes = kCFSocketConnectCallBack | kCFSocketReadCallBack | kCFSocketWriteCallBack; CFSocketContext context = {0,this,0,0,0}; socketInfo->socket =CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);if(CFSocketIsValid(socketInfo->socket) ==false) {qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");return;} CFOptionFlags flags =CFSocketGetSocketFlags(socketInfo->socket);// QSocketNotifier doesn't close the socket upon destruction/invalidation flags &= ~kCFSocketCloseOnInvalidate;// Explicitly disable automatic re-enable, as we do that manually on each runloop pass flags &= ~(kCFSocketAutomaticallyReenableWriteCallBack | kCFSocketAutomaticallyReenableReadCallBack);CFSocketSetSocketFlags(socketInfo->socket, flags); macSockets.insert(nativeSocket, socketInfo);}if(type ==QSocketNotifier::Read) {Q_ASSERT(socketInfo->readNotifier ==0); socketInfo->readNotifier = notifier; socketInfo->readEnabled =false;}else if(type ==QSocketNotifier::Write) {Q_ASSERT(socketInfo->writeNotifier ==0); socketInfo->writeNotifier = notifier; socketInfo->writeEnabled =false;}if(!enableNotifiersObserver) {// Create a run loop observer which enables the socket notifiers on each// pass of the run loop, before any sources are processed. CFRunLoopObserverContext context = {}; context.info =this; enableNotifiersObserver =CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeSources,true,0, enableSocketNotifiers, &context);Q_ASSERT(enableNotifiersObserver);CFRunLoopAddObserver(CFRunLoopGetCurrent(), enableNotifiersObserver, kCFRunLoopCommonModes);}}voidQCFSocketNotifier::unregisterSocketNotifier(QSocketNotifier *notifier){Q_ASSERT(notifier);int nativeSocket = notifier->socket();int type = notifier->type();#ifndef QT_NO_DEBUGif(nativeSocket <0|| nativeSocket > FD_SETSIZE) {qWarning("QSocketNotifier: Internal error");return;}else if(notifier->thread() != eventDispatcher->thread() || eventDispatcher->thread() !=QThread::currentThread()) {qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");return;}#endifif(type ==QSocketNotifier::Exception) {qWarning("QSocketNotifier::Exception is not supported on iOS");return;} MacSocketInfo *socketInfo = macSockets.value(nativeSocket);if(!socketInfo) {qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");return;}// Decrement read/write counters and disable callbacks if necessary.if(type ==QSocketNotifier::Read) {Q_ASSERT(notifier == socketInfo->readNotifier); socketInfo->readNotifier =0; socketInfo->readEnabled =false;CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);}else if(type ==QSocketNotifier::Write) {Q_ASSERT(notifier == socketInfo->writeNotifier); socketInfo->writeNotifier =0; socketInfo->writeEnabled =false;CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);}// Remove CFSocket from runloop if this was the last QSocketNotifier.if(socketInfo->readNotifier ==0&& socketInfo->writeNotifier ==0) {unregisterSocketInfo(socketInfo);delete socketInfo; macSockets.remove(nativeSocket);}}voidQCFSocketNotifier::removeSocketNotifiers(){// Remove CFSockets from the runloop.for(MacSocketInfo *socketInfo :std::as_const(macSockets)) {unregisterSocketInfo(socketInfo);delete socketInfo;} macSockets.clear();destroyRunLoopObserver();}voidQCFSocketNotifier::destroyRunLoopObserver(){if(!enableNotifiersObserver)return;CFRunLoopObserverInvalidate(enableNotifiersObserver);CFRelease(enableNotifiersObserver); enableNotifiersObserver =0;}voidQCFSocketNotifier::unregisterSocketInfo(MacSocketInfo *socketInfo){if(socketInfo->runloop) {if(CFSocketIsValid(socketInfo->socket))qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);CFRunLoopSourceInvalidate(socketInfo->runloop);CFRelease(socketInfo->runloop);}CFSocketInvalidate(socketInfo->socket);CFRelease(socketInfo->socket);}voidQCFSocketNotifier::enableSocketNotifiers(CFRunLoopObserverRef ref, CFRunLoopActivity activity,void*info){Q_UNUSED(ref);Q_UNUSED(activity);const QCFSocketNotifier *that =static_cast<QCFSocketNotifier *>(info);for(MacSocketInfo *socketInfo : that->macSockets) {if(!CFSocketIsValid(socketInfo->socket))continue;if(!socketInfo->runloop) {// Add CFSocket to runloop.if(!(socketInfo->runloop =qt_mac_add_socket_to_runloop(socketInfo->socket))) {qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");CFSocketInvalidate(socketInfo->socket);continue;}// Apple docs say: "If a callback is automatically re-enabled,// it is called every time the condition becomes true ... If a// callback is not automatically re-enabled, then it gets called// exactly once, and is not called again until you manually// re-enable that callback by calling CFSocketEnableCallBacks()".// So, we don't need to enable callbacks on registering. socketInfo->readEnabled = (socketInfo->readNotifier !=nullptr);if(!socketInfo->readEnabled)CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); socketInfo->writeEnabled = (socketInfo->writeNotifier !=nullptr);if(!socketInfo->writeEnabled)CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);continue;}if(socketInfo->readNotifier && !socketInfo->readEnabled) { socketInfo->readEnabled =true;CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);}if(socketInfo->writeNotifier && !socketInfo->writeEnabled) { socketInfo->writeEnabled =true;CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);}}} QT_END_NAMESPACE 
close