// // GTMSignalHandler.m // // Copyright 2008 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // #import "GTMSignalHandler.h" #import "GTMDefines.h" #import "GTMTypeCasting.h" #import // for kqueue() and kevent #import "GTMDebugSelectorValidation.h" // Simplifying assumption: No more than one handler for a particular signal is // alive at a time. When the second signal is registered, kqueue just updates // the info about the first signal, which makes -dealloc time complicated (what // happens when handler1(SIGUSR1) is released before handler2(SIGUSR1)?). This // could be solved by having one kqueue per signal, or keeping a list of // handlers interested in a particular signal, but not really worth it for apps // that register the handlers at startup and don't change them. // File descriptor for the kqueue that will hold all of our signal events. static int gSignalKQueueFileDescriptor = 0; // A wrapper around the kqueue file descriptor so we can put it into a // runloop. static CFSocketRef gRunLoopSocket = NULL; @interface GTMSignalHandler (PrivateMethods) - (void)notify; - (void)addFileDescriptorMonitor:(int)fd; - (void)registerWithKQueue; @end @implementation GTMSignalHandler -(id)init { // Folks shouldn't call init directly, so they get what they deserve. _GTMDevLog(@"Don't call init, use " @"initWithSignal:target:action:"); return [self initWithSignal:0 target:nil action:NULL]; } - (id)initWithSignal:(int)signo target:(id)target action:(SEL)action { if ((self = [super init])) { if (signo == 0) { [self release]; return nil; } signo_ = signo; target_ = target; // Don't retain since target will most likely retain us. action_ = action; GTMAssertSelectorNilOrImplementedWithArguments(target_, action_, @encode(int), NULL); // We're handling this signal via kqueue, so turn off the usual signal // handling. signal(signo_, SIG_IGN); if (action != NULL) { [self registerWithKQueue]; } } return self; } - (void)dealloc { [self invalidate]; [super dealloc]; } // Cribbed from Advanced Mac OS X Programming. static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { // We're using CFRunLoop calls here. Even when used on the main thread, they // don't trigger the draining of the main application's autorelease pool that // NSRunLoop provides. If we're used in a UI-less app, this means that // autoreleased objects would never go away, so we provide our own pool here. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; struct kevent event; if (kevent(gSignalKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) { _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE } else { GTMSignalHandler *handler = GTM_STATIC_CAST(GTMSignalHandler, event.udata); [handler notify]; } [pool drain]; } // Cribbed from Advanced Mac OS X Programming - (void)addFileDescriptorMonitor:(int)fd { CFSocketContext context = { 0, NULL, NULL, NULL, NULL }; gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault, fd, kCFSocketReadCallBack, SocketCallBack, &context); if (gRunLoopSocket == NULL) { _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE goto bailout; // COV_NF_LINE } CFRunLoopSourceRef rls; rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0); if (rls == NULL) { _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE goto bailout; // COV_NF_LINE } CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); bailout: return; } - (void)registerWithKQueue { // Make sure we have our kqueue. if (gSignalKQueueFileDescriptor == 0) { gSignalKQueueFileDescriptor = kqueue(); if (gSignalKQueueFileDescriptor == -1) { _GTMDevLog(@"could not make signal kqueue. Errno %d", errno); // COV_NF_LINE return; // COV_NF_LINE } // Add the kqueue file descriptor to the runloop. [self addFileDescriptorMonitor:gSignalKQueueFileDescriptor]; } // Add a new event for the signal. struct kevent filter; EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, self); const struct timespec noWait = { 0, 0 }; if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) { _GTMDevLog(@"could not add event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE } } - (void)invalidate { // Short-circuit cases where we didn't actually register a kqueue event. if (signo_ == 0) return; if (action_ == nil) return; struct kevent filter; EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_DELETE, 0, 0, self); const struct timespec noWait = { 0, 0 }; if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) { _GTMDevLog(@"could not remove event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE } // Set action_ to nil so that if invalidate is called on us twice, // nothing happens. action_ = nil; } - (void)notify { // Now, fire the selector NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_]; _GTMDevAssert(methodSig != nil, @"failed to get the signature?"); NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; [invocation setTarget:target_]; [invocation setSelector:action_]; [invocation setArgument:&signo_ atIndex:2]; [invocation invoke]; } @end