#include "talk/base/macsocketserver.h" #include "talk/base/common.h" #include "talk/base/logging.h" #include "talk/base/macasyncsocket.h" #include "talk/base/macutils.h" #include "talk/base/thread.h" namespace talk_base { /////////////////////////////////////////////////////////////////////////////// // MacBaseSocketServer /////////////////////////////////////////////////////////////////////////////// MacBaseSocketServer::MacBaseSocketServer() { } MacBaseSocketServer::~MacBaseSocketServer() { } AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int type) { return CreateAsyncSocket(AF_INET, type); } AsyncSocket* MacBaseSocketServer::CreateAsyncSocket(int family, int type) { if (SOCK_STREAM != type) return NULL; MacAsyncSocket* socket = new MacAsyncSocket(this, family); if (!socket->valid()) { delete socket; return NULL; } return socket; } void MacBaseSocketServer::RegisterSocket(MacAsyncSocket* s) { sockets_.insert(s); } void MacBaseSocketServer::UnregisterSocket(MacAsyncSocket* s) { VERIFY(1 == sockets_.erase(s)); // found 1 } bool MacBaseSocketServer::SetPosixSignalHandler(int signum, void (*handler)(int)) { Dispatcher* dispatcher = signal_dispatcher(); if (!PhysicalSocketServer::SetPosixSignalHandler(signum, handler)) { return false; } // Only register the FD once, when the first custom handler is installed. if (!dispatcher && (dispatcher = signal_dispatcher())) { CFFileDescriptorContext ctx = { 0 }; ctx.info = this; CFFileDescriptorRef desc = CFFileDescriptorCreate( kCFAllocatorDefault, dispatcher->GetDescriptor(), false, &MacBaseSocketServer::FileDescriptorCallback, &ctx); if (!desc) { return false; } CFFileDescriptorEnableCallBacks(desc, kCFFileDescriptorReadCallBack); CFRunLoopSourceRef ref = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, desc, 0); if (!ref) { CFRelease(desc); return false; } CFRunLoopAddSource(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes); CFRelease(desc); CFRelease(ref); } return true; } // Used to disable socket events from waking our message queue when // process_io is false. Does not disable signal event handling though. void MacBaseSocketServer::EnableSocketCallbacks(bool enable) { for (std::set::iterator it = sockets().begin(); it != sockets().end(); ++it) { if (enable) { (*it)->EnableCallbacks(); } else { (*it)->DisableCallbacks(); } } } void MacBaseSocketServer::FileDescriptorCallback(CFFileDescriptorRef fd, CFOptionFlags flags, void* context) { MacBaseSocketServer* this_ss = reinterpret_cast(context); ASSERT(this_ss); Dispatcher* signal_dispatcher = this_ss->signal_dispatcher(); ASSERT(signal_dispatcher); signal_dispatcher->OnPreEvent(DE_READ); signal_dispatcher->OnEvent(DE_READ, 0); CFFileDescriptorEnableCallBacks(fd, kCFFileDescriptorReadCallBack); } /////////////////////////////////////////////////////////////////////////////// // MacCFSocketServer /////////////////////////////////////////////////////////////////////////////// void WakeUpCallback(void* info) { MacCFSocketServer* server = static_cast(info); ASSERT(NULL != server); server->OnWakeUpCallback(); } MacCFSocketServer::MacCFSocketServer() : run_loop_(CFRunLoopGetCurrent()), wake_up_(NULL) { CFRunLoopSourceContext ctx; memset(&ctx, 0, sizeof(ctx)); ctx.info = this; ctx.perform = &WakeUpCallback; wake_up_ = CFRunLoopSourceCreate(NULL, 0, &ctx); ASSERT(NULL != wake_up_); if (wake_up_) { CFRunLoopAddSource(run_loop_, wake_up_, kCFRunLoopCommonModes); } } MacCFSocketServer::~MacCFSocketServer() { if (wake_up_) { CFRunLoopSourceInvalidate(wake_up_); CFRelease(wake_up_); } } bool MacCFSocketServer::Wait(int cms, bool process_io) { ASSERT(CFRunLoopGetCurrent() == run_loop_); if (!process_io && cms == 0) { // No op. return true; } if (!process_io) { // No way to listen to common modes and not get socket events, unless // we disable each one's callbacks. EnableSocketCallbacks(false); } SInt32 result; if (kForever == cms) { do { // Would prefer to run in a custom mode that only listens to wake_up, // but we have qtkit sending work to the main thread which is effectively // blocked here, causing deadlock. Thus listen to the common modes. // TODO: If QTKit becomes thread safe, do the above. result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000000, false); } while (result != kCFRunLoopRunFinished && result != kCFRunLoopRunStopped); } else { // TODO: In the case of 0ms wait, this will only process one event, so we // may want to loop until it returns TimedOut. CFTimeInterval seconds = cms / 1000.0; result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, seconds, false); } if (!process_io) { // Reenable them. Hopefully this won't cause spurious callbacks or // missing ones while they were disabled. EnableSocketCallbacks(true); } if (kCFRunLoopRunFinished == result) { return false; } return true; } void MacCFSocketServer::WakeUp() { if (wake_up_) { CFRunLoopSourceSignal(wake_up_); CFRunLoopWakeUp(run_loop_); } } void MacCFSocketServer::OnWakeUpCallback() { ASSERT(run_loop_ == CFRunLoopGetCurrent()); CFRunLoopStop(run_loop_); } /////////////////////////////////////////////////////////////////////////////// // MacCarbonSocketServer /////////////////////////////////////////////////////////////////////////////// #ifndef CARBON_DEPRECATED const UInt32 kEventClassSocketServer = 'MCSS'; const UInt32 kEventWakeUp = 'WAKE'; const EventTypeSpec kEventWakeUpSpec[] = { { kEventClassSocketServer, kEventWakeUp } }; std::string DecodeEvent(EventRef event) { std::string str; DecodeFourChar(::GetEventClass(event), &str); str.push_back(':'); DecodeFourChar(::GetEventKind(event), &str); return str; } MacCarbonSocketServer::MacCarbonSocketServer() : event_queue_(GetCurrentEventQueue()), wake_up_(NULL) { VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, kEventAttributeUserEvent, &wake_up_)); } MacCarbonSocketServer::~MacCarbonSocketServer() { if (wake_up_) { ReleaseEvent(wake_up_); } } bool MacCarbonSocketServer::Wait(int cms, bool process_io) { ASSERT(GetCurrentEventQueue() == event_queue_); // Listen to all events if we're processing I/O. // Only listen for our wakeup event if we're not. UInt32 num_types = 0; const EventTypeSpec* events = NULL; if (!process_io) { num_types = GetEventTypeCount(kEventWakeUpSpec); events = kEventWakeUpSpec; } EventTargetRef target = GetEventDispatcherTarget(); EventTimeout timeout = (kForever == cms) ? kEventDurationForever : cms / 1000.0; EventTimeout end_time = GetCurrentEventTime() + timeout; bool done = false; while (!done) { EventRef event; OSStatus result = ReceiveNextEvent(num_types, events, timeout, true, &event); if (noErr == result) { if (wake_up_ != event) { LOG_F(LS_VERBOSE) << "Dispatching event: " << DecodeEvent(event); result = SendEventToEventTarget(event, target); if ((noErr != result) && (eventNotHandledErr != result)) { LOG_E(LS_ERROR, OS, result) << "SendEventToEventTarget"; } } else { done = true; } ReleaseEvent(event); } else if (eventLoopTimedOutErr == result) { ASSERT(cms != kForever); done = true; } else if (eventLoopQuitErr == result) { // Ignore this... we get spurious quits for a variety of reasons. LOG_E(LS_VERBOSE, OS, result) << "ReceiveNextEvent"; } else { // Some strange error occurred. Log it. LOG_E(LS_WARNING, OS, result) << "ReceiveNextEvent"; return false; } if (kForever != cms) { timeout = end_time - GetCurrentEventTime(); } } return true; } void MacCarbonSocketServer::WakeUp() { if (!IsEventInQueue(event_queue_, wake_up_)) { RetainEvent(wake_up_); OSStatus result = PostEventToQueue(event_queue_, wake_up_, kEventPriorityStandard); if (noErr != result) { LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; } } } /////////////////////////////////////////////////////////////////////////////// // MacCarbonAppSocketServer /////////////////////////////////////////////////////////////////////////////// MacCarbonAppSocketServer::MacCarbonAppSocketServer() : event_queue_(GetCurrentEventQueue()) { // Install event handler VERIFY(noErr == InstallApplicationEventHandler( NewEventHandlerUPP(WakeUpEventHandler), 1, kEventWakeUpSpec, this, &event_handler_)); // Install a timer and set it idle to begin with. VERIFY(noErr == InstallEventLoopTimer(GetMainEventLoop(), kEventDurationForever, kEventDurationForever, NewEventLoopTimerUPP(TimerHandler), this, &timer_)); } MacCarbonAppSocketServer::~MacCarbonAppSocketServer() { RemoveEventLoopTimer(timer_); RemoveEventHandler(event_handler_); } OSStatus MacCarbonAppSocketServer::WakeUpEventHandler( EventHandlerCallRef next, EventRef event, void *data) { QuitApplicationEventLoop(); return noErr; } void MacCarbonAppSocketServer::TimerHandler( EventLoopTimerRef timer, void *data) { QuitApplicationEventLoop(); } bool MacCarbonAppSocketServer::Wait(int cms, bool process_io) { if (!process_io && cms == 0) { // No op. return true; } if (kForever != cms) { // Start a timer. OSStatus error = SetEventLoopTimerNextFireTime(timer_, cms / 1000.0); if (error != noErr) { LOG(LS_ERROR) << "Failed setting next fire time."; } } if (!process_io) { // No way to listen to common modes and not get socket events, unless // we disable each one's callbacks. EnableSocketCallbacks(false); } RunApplicationEventLoop(); if (!process_io) { // Reenable them. Hopefully this won't cause spurious callbacks or // missing ones while they were disabled. EnableSocketCallbacks(true); } return true; } void MacCarbonAppSocketServer::WakeUp() { // TODO: No-op if there's already a WakeUp in flight. EventRef wake_up; VERIFY(noErr == CreateEvent(NULL, kEventClassSocketServer, kEventWakeUp, 0, kEventAttributeUserEvent, &wake_up)); OSStatus result = PostEventToQueue(event_queue_, wake_up, kEventPriorityStandard); if (noErr != result) { LOG_E(LS_ERROR, OS, result) << "PostEventToQueue"; } ReleaseEvent(wake_up); } #endif } // namespace talk_base