PageRenderTime 87ms CodeModel.GetById 19ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/google-toolbox-for-mac/Foundation/GTMAbstractDOListener.m

http://macfuse.googlecode.com/
Objective C | 454 lines | 296 code | 84 blank | 74 comment | 48 complexity | a6aa012df9e9685d64e7ccef8c94ba40 MD5 | raw file
  1//
  2//  GTMAbstractDOListener.m
  3//
  4//  Copyright 2006-2009 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9//
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11//
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19#import "GTMAbstractDOListener.h"
 20#import "GTMSystemVersion.h"
 21#import <mach/mach_init.h>
 22
 23// Hack workaround suggested by DTS for the DO deadlock bug.  Basically, this
 24// class intercepts the delegate role for DO's receive port (which is an
 25// NSMachPort).  When -handlePortMessage: is called, it verifies that the send
 26// and receive ports are not nil, then forwards the message on to the original
 27// delegate.  If the ports are nil, then the resulting NSConnection would
 28// eventually cause us to deadlock.  In this case, it simply ignores the
 29// message.  This is only need on Tiger.
 30#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 31@interface GTMReceivePortDelegate : NSObject {
 32  __weak id delegate_;
 33}
 34- (id)initWithDelegate:(id)delegate;
 35- (void)handlePortMessage:(NSPortMessage *)message;
 36@end
 37#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 38
 39@interface GTMAbstractDOListener (PrivateMethods)
 40- (BOOL)startListening;
 41- (void)stopListening;
 42
 43// Returns a description of the port based on the type of port.
 44- (NSString *)portDescription;
 45
 46#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 47// Uses the GTMReceivePortDelegate hack (see comments above) if we're on Tiger.
 48- (void)hackaroundTigerDOWedgeBug:(NSConnection *)conn;
 49#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
 50@end
 51
 52// Static global set that holds a pointer to all instances of
 53// GTMAbstractDOListener subclasses.
 54//
 55static NSMutableSet *gAllListeners = nil;
 56
 57@implementation GTMAbstractDOListener
 58
 59+ (void)initialize {
 60  if (self == [GTMAbstractDOListener class]) {
 61    // We create the set using CFSetCreateMutable because we don't
 62    // want to retain things in this set. If we retained things in the
 63    // set we would never be able to dealloc ourselves because we
 64    // add "self" to this set in it's init routine would cause an
 65    // extra retain to be added to it.
 66    gAllListeners = (NSMutableSet*)CFSetCreateMutable(NULL, 0, NULL);
 67  }
 68}
 69
 70+ (NSArray *)allListeners {
 71  // We return an NSArray instead of an NSSet here because NSArrays look nicer
 72  // when displayed as %@
 73  NSArray *allListeners = nil;
 74
 75  @synchronized (gAllListeners) {
 76    allListeners = [gAllListeners allObjects];
 77  }
 78  return allListeners;
 79}
 80
 81- (id)init {
 82  return [self initWithRegisteredName:nil protocol:NULL];
 83}
 84
 85- (id)initWithRegisteredName:(NSString *)name protocol:(Protocol *)proto {
 86  return [self initWithRegisteredName:name
 87                             protocol:proto
 88                                 port:[NSMachPort port]];
 89}
 90
 91- (id)initWithRegisteredName:(NSString *)name
 92                    protocol:(Protocol *)proto
 93                        port:(NSPort *)port {
 94  self = [super init];
 95  if (!self) {
 96    return nil;
 97  }
 98
 99  if ((!proto) || (!port) || (!name)) {
100    if (!proto) {
101      _GTMDevLog(@"Failed to create a listener, a protocol must be specified");
102    }
103
104    if (!port) {
105      _GTMDevLog(@"Failed to create a listener, a port must be specified");
106    }
107
108    if (!name) {
109      _GTMDevLog(@"Failed to create a listener, a name must be specified");
110    }
111
112    [self release];
113    return nil;
114  }
115
116  registeredName_ = [name copy];
117  protocol_ = proto;  // Can't retain protocols
118  port_ = [port retain];
119
120  requestTimeout_ = -1;
121  replyTimeout_ = -1;
122
123  heartRate_ = (NSTimeInterval)10.0;
124
125  _GTMDevAssert(gAllListeners, @"gAllListeners is not nil");
126  @synchronized (gAllListeners) {
127    [gAllListeners addObject:self];
128  }
129
130  return self;
131}
132
133- (void)dealloc {
134  _GTMDevAssert(gAllListeners, @"gAllListeners is not nil");
135  @synchronized (gAllListeners) {
136    [gAllListeners removeObject:self];
137  }
138
139#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
140  [receivePortDelegate_ release];
141#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
142  
143  [self shutdown];
144  [port_ release];
145  [registeredName_ release];
146  [super dealloc];
147}
148
149
150#pragma mark Getters and Setters
151
152- (NSString *)registeredName {
153  return registeredName_;
154}
155
156- (void)setRegisteredName:(NSString *)name {
157  if (!name) {
158    return;
159  }
160  [registeredName_ autorelease];
161  registeredName_ = [name copy];
162}
163
164- (NSTimeInterval)requestTimeout {
165  return requestTimeout_;
166}
167
168- (void)setRequestTimeout:(NSTimeInterval)timeout {
169  requestTimeout_ = timeout;
170}
171
172- (NSTimeInterval)replyTimeout {
173  return replyTimeout_;
174}
175
176- (void)setReplyTimeout:(NSTimeInterval)timeout {
177  replyTimeout_ = timeout;
178}
179
180- (void)setThreadHeartRate:(NSTimeInterval)heartRate {
181  heartRate_ = heartRate;
182}
183
184- (NSTimeInterval)ThreadHeartRate {
185  return heartRate_;
186}
187
188- (NSConnection *)connection {
189  return connection_;
190}
191
192- (NSString *)description {
193  return [NSString stringWithFormat:@"%@<%p> { name=\"%@\", %@ }",
194            [self class], self, registeredName_, [self portDescription]];
195}
196
197#pragma mark "Run" methods
198
199- (BOOL)runInCurrentThread {
200  return [self startListening];
201}
202
203- (void)runInNewThreadWithErrorTarget:(id)errObject
204                             selector:(SEL)selector
205                   withObjectArgument:(id)argument {
206  NSInvocation *invocation = nil;
207  
208  _GTMDevAssert(((errObject != nil && selector != NULL) ||
209                 (!errObject && !selector)), @"errObject and selector must "
210                @"both be nil or not nil");
211
212  // create an invocation we can use if things fail
213  if (errObject) {
214    NSMethodSignature *signature =
215    [errObject methodSignatureForSelector:selector];
216    invocation = [NSInvocation invocationWithMethodSignature:signature];
217    [invocation setSelector:selector];
218    [invocation setTarget:errObject];
219
220    // If the selector they passed in takes an arg (i.e., it has at least one 
221    // colon in the selector name), then set the first user-specified arg to be
222    // the |argument| they specified.  The first two args are self and _cmd.
223    if ([signature numberOfArguments] > 2) {
224      [invocation setArgument:&argument atIndex:2];
225    }
226
227    [invocation retainArguments];
228  }
229
230  shouldShutdown_ = NO;
231  [NSThread detachNewThreadSelector:@selector(threadMain:)
232                           toTarget:self
233                         withObject:invocation];
234}
235
236- (void)shutdown {
237  // If we're not running in a new thread (then we're running in the "current"
238  // thread), tear down the NSConnection here.  If we are running in a new
239  // thread we just set the shouldShutdown_ flag, and the thread will teardown
240  // the NSConnection itself.
241  if (!isRunningInNewThread_) {
242    [self stopListening];
243  } else {
244    shouldShutdown_ = YES;
245  }
246}
247
248@end
249
250@implementation GTMAbstractDOListener (PrivateMethods)
251
252- (BOOL)startListening {
253  BOOL result = NO;
254
255  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
256  
257  _GTMDevAssert(!connection_, @"Connection_ should not be set. Was this "
258                @"listener already started? %@", self);
259  connection_ = [[NSConnection alloc] initWithReceivePort:port_ sendPort:nil];
260
261  NSProtocolChecker *checker =
262  [NSProtocolChecker protocolCheckerWithTarget:self
263                                      protocol:protocol_];
264
265  if (requestTimeout_ >= 0) {
266    [connection_ setRequestTimeout:requestTimeout_];
267  }
268
269  if (replyTimeout_ >= 0) {
270    [connection_ setReplyTimeout:replyTimeout_];
271  }
272
273  // Set the connection's root object to be the protocol checker so that only
274  // methods listed in the protocol_ are available via DO.
275  [connection_ setRootObject:checker];
276
277  // Allow subclasses to be the connection delegate
278  [connection_ setDelegate:self];
279
280  // Because of radar 5493309 we need to do this. [NSConnection registeredName:]
281  // returns NO when the connection is created using an NSSocketPort under
282  // Leopard.
283  //
284  // The recommendation from Apple was to use the command:
285  // [NSConnection registerName:withNameServer:].
286  NSPortNameServer *server;
287  if ([port_ isKindOfClass:[NSSocketPort class]]) {
288    server = [NSSocketPortNameServer sharedInstance];
289  } else {
290    server = [NSPortNameServer systemDefaultPortNameServer];
291  }
292
293  BOOL registered = [connection_ registerName:registeredName_
294                               withNameServer:server];
295
296  if (registeredName_ && registered) {
297#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
298    [self hackaroundTigerDOWedgeBug:connection_];
299#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
300
301    result = YES;
302
303    _GTMDevLog(@"listening on %@ with name '%@'", [self portDescription],
304               registeredName_);
305  } else {
306    _GTMDevLog(@"failed to register %@ with %@", connection_, registeredName_);
307  }
308
309  // we're good, so call the overrideable initializer
310  if (result) {
311    // Call the virtual "runIn*" initializer
312    result = [self doRunInitialization];
313  } else {
314    [connection_ invalidate];
315    [connection_ release];
316    connection_ = nil;
317  }
318
319  [pool drain];
320
321  return result;
322}
323
324- (void)stopListening {
325  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
326  [connection_ invalidate];
327  [connection_ release];
328  connection_ = nil;
329  [pool drain];
330}
331
332- (NSString *)portDescription {
333  NSString *portDescription;
334  if ([port_ isKindOfClass:[NSMachPort class]]) {
335    portDescription = [NSString stringWithFormat:@"mach_port=%#x",
336                       [(NSMachPort *)port_ machPort]];
337  } else {
338    portDescription = [NSString stringWithFormat:@"port=%@",
339                       [port_ description]];
340  }
341  return portDescription;
342}
343
344#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
345- (void)hackaroundTigerDOWedgeBug:(NSConnection *)conn {
346  if ([GTMSystemVersion isTiger]) {
347    NSPort *receivePort = [conn receivePort];
348    if ([receivePort isKindOfClass:[NSMachPort class]]) {
349      id portDelegate = [receivePort delegate];
350      receivePortDelegate_ =
351        [[GTMReceivePortDelegate alloc] initWithDelegate:portDelegate];
352      [receivePort setDelegate:receivePortDelegate_];
353    }
354  }
355}
356#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
357
358@end
359
360@implementation GTMAbstractDOListener (GTMAbstractDOListenerSubclassMethods)
361
362- (BOOL)doRunInitialization {
363  return YES;
364}
365
366//
367// -threadMain:
368//
369
370//
371- (void)threadMain:(NSInvocation *)failureCallback {
372  isRunningInNewThread_ = YES;
373
374  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
375
376  // register
377  if ([self startListening]) {
378    // spin
379    for (;;) {  // Run forever
380
381      // check if we were asked to shutdown
382      if (shouldShutdown_) {
383        break;
384      }
385
386      NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
387      // Wrap our runloop in case we get an exception from DO
388      @try {
389        NSDate *waitDate = [NSDate dateWithTimeIntervalSinceNow:heartRate_];
390        [[NSRunLoop currentRunLoop] runUntilDate:waitDate];
391      } @catch (id e) {
392        _GTMDevLog(@"Listener '%@' caught exception: %@", registeredName_, e);
393      }
394      [localPool drain];
395    }
396  } else {
397    // failed, if we had something to invoke, call it on the main thread
398    if (failureCallback) {
399      [failureCallback performSelectorOnMainThread:@selector(invoke)
400                                        withObject:nil
401                                     waitUntilDone:NO];
402    }
403  }
404
405  [self stopListening];
406  [pool drain];
407
408  isRunningInNewThread_ = NO;
409}
410
411@end
412
413#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
414@implementation GTMReceivePortDelegate
415
416- (id)initWithDelegate:(id)delegate {
417  if ((self = [super init])) {
418    delegate_ = delegate;  // delegates aren't retained
419  }
420  return self;
421}
422
423- (void)handlePortMessage:(NSPortMessage *)message {
424  NSPort *receivePort = [message receivePort];
425  NSPort *sendPort    = [message sendPort];
426
427  // If we don't have a sensible send or receive port, just act like 
428  // the message never arrived.  Otherwise, hand it off to the original 
429  // delegate (which is the NSMachPort itself).
430  if (receivePort == nil || sendPort == nil || [receivePort isEqual:sendPort]) {
431    _GTMDevLog(@"Dropping port message destined for itself to avoid DO wedge.");
432  } else {
433    // Uncomment for super-duper verbose DO message forward logging
434    // _GTMDevLog(@"--> Forwarding message %@ to delegate %@",
435    //            message, delegate_);
436    [delegate_ handlePortMessage:message];
437  }
438
439  // If processing the message caused us to drop no longer being the delegate, 
440  // set us back.  Due to interactions between NSConnection and NSMachPort, 
441  // it's possible for the NSMachPort's delegate to get set back to its 
442  // original value.  If that happens, we set it back to the value we want.
443  if ([delegate_ delegate] != self) {
444    if ([delegate_ delegate] == delegate_) {
445      _GTMDevLog(@"Restoring DO delegate to %@", self);
446      [delegate_ setDelegate:self];
447    } else {
448      _GTMDevLog(@"GMReceivePortDelegate replaced with %@",
449                 [delegate_ delegate]);
450    }
451  }
452}
453@end
454#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4