aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMSignalHandler.m
blob: 05ee8262206ebc8280cb53122e3a1bfd727aae9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//
//  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