aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMTransientRootProxy.m
blob: 2f7dfecf36421ff567b3e4678de3b940c73ae85d (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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//
//  GTMTransientRootProxy.m
//
//  Copyright 2006-2009 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 "GTMTransientRootProxy.h"
#import "GTMObjC2Runtime.h"

// Private methods on NSMethodSignature that we need to call.  This method has
// been available since 10.0, but Apple didn't add it to the headers until 10.5
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
@interface NSMethodSignature (UndeclaredMethods)
+ (NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4

@interface GTMTransientRootProxy (PrivateMethods)
// Returns an NSConnection for NSMacPorts.  This method is broken out to allow
// subclasses to override it to generate different types of NSConnections.
- (NSConnection *)makeConnection;

// Returns the "real" proxy (stored in the realProxy_ ivar) associated with this
// instance.  If realProxy_ is nil, then an attempt is made to make a connection
// to create the realProxy_.
//
- (NSDistantObject *)realProxy;

// "Releases" the realProxy_ ivar, and removes |self| as an observer from
// the NSNotificationCenter.
//
- (void)releaseRealProxy;

// Notification that a connection has died.
- (void)connectionDidDie:(NSNotification *)notification;
@end

@implementation GTMTransientRootProxy

+ (id)rootProxyWithRegisteredName:(NSString *)name
                             host:(NSString *)host
                         protocol:(Protocol *)protocol
                   requestTimeout:(NSTimeInterval)requestTimeout
                     replyTimeout:(NSTimeInterval)replyTimeout {
  return [[[self alloc] initWithRegisteredName:name
                                          host:host
                                      protocol:protocol
                                requestTimeout:requestTimeout
                                  replyTimeout:replyTimeout] autorelease];
}

- (id)initWithRegisteredName:(NSString *)name
                        host:(NSString *)host
                    protocol:(Protocol *)protocol
              requestTimeout:(NSTimeInterval)requestTimeout
                replyTimeout:(NSTimeInterval)replyTimeout {
  if (!name || !protocol) {
    [self release];
    return nil;
  }

  requestTimeout_ = requestTimeout;
  replyTimeout_ = replyTimeout;

  registeredName_ = [name copy];
  host_ = [host copy];

  protocol_ = protocol;  // Protocols can't be retained

  return self;
}

- (id)init {
  return [self initWithRegisteredName:nil
                                 host:nil
                             protocol:nil
                       requestTimeout:0.0
                         replyTimeout:0.0];
}

- (void)dealloc {
  [self releaseRealProxy];
  [registeredName_ release];
  [host_ release];
  [super dealloc];
}

- (BOOL)isConnected {
  BOOL result = NO;
  @synchronized (self) {
    result = [[[self realProxy] connectionForProxy] isValid];
  }
  return result;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  struct objc_method_description mdesc;
  mdesc = protocol_getMethodDescription(protocol_, selector, YES, YES);
  NSMethodSignature *returnValue = nil;
  if (mdesc.types == NULL) {
    // COV_NF_START
    _GTMDevLog(@"Unable to get the protocol method description.  Returning "
               @"nil.");
    // COV_NF_END
  } else {
    returnValue = [NSMethodSignature signatureWithObjCTypes:mdesc.types];
  }
  return returnValue;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
  @try {
    NSDistantObject *target = [self realProxy];
    [invocation invokeWithTarget:target];

    // We need to catch NSException* here rather than "id" because we need to
    // treat |ex| as an NSException when using the -name method.  Also, we're
    // only looking to catch a few types of exception here, all of which are
    // NSException types; the rest we just rethrow.
  } @catch (NSException *ex) {
    NSString *exName = [ex name];
    // If we catch an exception who's name matches any of the following types,
    // it's because the DO connection probably went down.  So, we'll just
    // release our realProxy_, and attempt to reconnect on the next call.
    if ([exName isEqualToString:NSPortTimeoutException]
        || [exName isEqualToString:NSInvalidSendPortException]
        || [exName isEqualToString:NSInvalidReceivePortException]
        || [exName isEqualToString:NSFailedAuthenticationException]
        || [exName isEqualToString:NSPortSendException]
        || [exName isEqualToString:NSPortReceiveException]) {
      [self releaseRealProxy];  // COV_NF_LINE
    } else {
      // If the exception was any other type (commonly
      // NSInvalidArgumentException) then we'll just re-throw it to the caller.
      @throw;
    }
  }  // COV_NF_LINE
}

@end

@implementation GTMTransientRootProxy (PrivateMethods)

- (NSConnection *)makeConnection {
  return [NSConnection connectionWithRegisteredName:registeredName_ host:host_];
}

- (NSDistantObject *)realProxy {
  NSDistantObject *returnProxy = nil;

  @synchronized (self) {
    // No change so no notification
    if (realProxy_) return realProxy_;

    NSConnection *conn = [self makeConnection];
    [conn setRequestTimeout:requestTimeout_];
    [conn setReplyTimeout:replyTimeout_];
    @try {
      // Try to get the root proxy for this connection's vended object.
      realProxy_ = [conn rootProxy];
    } @catch (id ex) {
      // We may fail here if we can't get the root proxy in the amount of time
      // specified by the timeout above.  This may happen, for example, if the
      // server process is stopped (via SIGSTOP).  We'll just ignore this, and
      // try again at the next message.
      [conn invalidate];
      return nil;
    }
    if (!realProxy_) {
      [conn invalidate];
      // Again, no change in connection status
      return nil;
    }
    [realProxy_ retain];
    [realProxy_ setProtocolForProxy:protocol_];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self
           selector:@selector(connectionDidDie:)
               name:NSConnectionDidDieNotification
             object:conn];
    // Retain/autorelease so it lives at least the duration of this synchronize
    returnProxy = [[realProxy_ retain] autorelease];
  }  // @synchronized (self)

  return returnProxy;
}

- (void)connectionDidDie:(NSNotification *)notification {
  [self releaseRealProxy];
}

- (void)releaseRealProxy {
  @synchronized (self) {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    // Only trigger if we had a proxy before
    if (realProxy_) {
      [realProxy_ release];
      realProxy_ = nil;
    }
  }
}

@end

@implementation GTMRootProxyCatchAll

- (void)forwardInvocation:(NSInvocation *)invocation {
  @try {
    [super forwardInvocation:invocation];
  }
  @catch (id ex) {
    // Log for developers, but basically ignore it.
    _GTMDevLog(@"Proxy for invoking %@ has caught and is ignoring exception: %@",
               NSStringFromSelector([invocation selector]), ex);
  }
}

@end