blob: 26a06f18f2556cc1d0d0c8121dd6a4c43bddc48d [file] [log] [blame]
//
// 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 <sys/event.h> // 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