Implemented new close protocol with 'bye' meta-message.
authorJens Alfke <jens@mooseyard.com>
Thu Jun 19 16:22:05 2008 -0700 (2008-06-19)
changeset 183be241de1630
parent 17 70590cc555aa
child 19 16454d63d4c2
Implemented new close protocol with 'bye' meta-message.
BLIP/BLIPConnection.h
BLIP/BLIPConnection.m
BLIP/BLIPMessage.m
BLIP/BLIPReader.m
BLIP/BLIPRequest.m
BLIP/BLIPTest.m
BLIP/BLIPWriter.m
BLIP/BLIP_Internal.h
TCP/TCPConnection.h
TCP/TCPConnection.m
TCP/TCPStream.h
TCP/TCPStream.m
TCP/TCP_Internal.h
     1.1 --- a/BLIP/BLIPConnection.h	Thu Jun 19 10:22:19 2008 -0700
     1.2 +++ b/BLIP/BLIPConnection.h	Thu Jun 19 16:22:05 2008 -0700
     1.3 @@ -20,6 +20,7 @@
     1.4  @interface BLIPConnection : TCPConnection
     1.5  {
     1.6      BLIPDispatcher *_dispatcher;
     1.7 +    BOOL _blipClosing;
     1.8  }
     1.9  
    1.10  /** The delegate object that will be called when the connection opens, closes or receives messages. */
    1.11 @@ -73,6 +74,13 @@
    1.12  /** Called when a BLIPResponse (to one of your requests) is received from the peer.
    1.13      This is called <i>after</i> the response object's onComplete target, if any, is invoked.*/
    1.14  - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response;
    1.15 +
    1.16 +/** Called when the peer wants to close the connection. Return YES to allow, NO to prevent. */
    1.17 +- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
    1.18 +
    1.19 +/** Called if the peer refuses a close request. 
    1.20 +    The typical error is BLIP error kBLIPError_Forbidden. */
    1.21 +- (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error;
    1.22  @end
    1.23  
    1.24  
     2.1 --- a/BLIP/BLIPConnection.m	Thu Jun 19 10:22:19 2008 -0700
     2.2 +++ b/BLIP/BLIPConnection.m	Thu Jun 19 16:22:05 2008 -0700
     2.3 @@ -15,6 +15,7 @@
     2.4  #import "Logging.h"
     2.5  #import "Test.h"
     2.6  #import "ExceptionUtils.h"
     2.7 +#import "Target.h"
     2.8  
     2.9  
    2.10  NSString* const BLIPErrorDomain = @"BLIP";
    2.11 @@ -33,10 +34,14 @@
    2.12  }
    2.13  
    2.14  
    2.15 +@interface BLIPConnection ()
    2.16 +- (void) _handleCloseRequest: (BLIPRequest*)request;
    2.17 +@end
    2.18  
    2.19  
    2.20  @implementation BLIPConnection
    2.21  
    2.22 +
    2.23  - (void) dealloc
    2.24  {
    2.25      [_dispatcher release];
    2.26 @@ -48,6 +53,11 @@
    2.27  - (id<BLIPConnectionDelegate>) delegate                     {return (id)_delegate;}
    2.28  - (void) setDelegate: (id<BLIPConnectionDelegate>)delegate  {_delegate = delegate;}
    2.29  
    2.30 +
    2.31 +#pragma mark -
    2.32 +#pragma mark RECEIVING:
    2.33 +
    2.34 +
    2.35  - (BLIPDispatcher*) dispatcher
    2.36  {
    2.37      if( ! _dispatcher ) {
    2.38 @@ -58,11 +68,23 @@
    2.39  }
    2.40  
    2.41  
    2.42 +- (void) _dispatchMetaRequest: (BLIPRequest*)request
    2.43 +{
    2.44 +    NSString* profile = request.profile;
    2.45 +    if( [profile isEqualToString: kBLIPProfile_Bye] )
    2.46 +        [self _handleCloseRequest: request];
    2.47 +    else
    2.48 +        [request respondWithErrorCode: kBLIPError_NotFound message: @"Unknown meta profile"];
    2.49 +}
    2.50 +
    2.51 +
    2.52  - (void) _dispatchRequest: (BLIPRequest*)request
    2.53  {
    2.54      LogTo(BLIP,@"Received all of %@",request.descriptionWithProperties);
    2.55      @try{
    2.56 -        if( ! [self.dispatcher dispatchMessage: request] )
    2.57 +        if( request._flags & kBLIP_Meta )
    2.58 +            [self _dispatchMetaRequest: request];
    2.59 +        else if( ! [self.dispatcher dispatchMessage: request] )
    2.60              [self tellDelegate: @selector(connection:receivedRequest:) withObject: request];
    2.61          if( ! request.noReply && ! request.repliedTo ) {
    2.62              LogTo(BLIP,@"Returning default empty response to %@",request);
    2.63 @@ -81,6 +103,10 @@
    2.64  }
    2.65  
    2.66  
    2.67 +#pragma mark -
    2.68 +#pragma mark SENDING:
    2.69 +
    2.70 +
    2.71  - (BLIPRequest*) request
    2.72  {
    2.73      return [[[BLIPRequest alloc] _initWithConnection: self body: nil properties: nil] autorelease];
    2.74 @@ -103,11 +129,61 @@
    2.75  }
    2.76  
    2.77  
    2.78 +#pragma mark -
    2.79 +#pragma mark CLOSING:
    2.80 +
    2.81 +
    2.82 +- (void) _beginClose
    2.83 +{
    2.84 +    // Override of TCPConnection method. Instead of closing the socket, send a 'bye' request:
    2.85 +    if( ! _blipClosing ) {
    2.86 +        LogTo(BLIPVerbose,@"Sending close request...");
    2.87 +        BLIPRequest *r = [self request];
    2.88 +        [r _setFlag: kBLIP_Meta value: YES];
    2.89 +        r.profile = kBLIPProfile_Bye;
    2.90 +        BLIPResponse *response = [r send];
    2.91 +        response.onComplete = $target(self,_receivedCloseResponse:);
    2.92 +    }
    2.93 +    // Put the writer in close mode, to prevent client from sending any more requests:
    2.94 +    [self.writer close];
    2.95 +}
    2.96 +
    2.97 +- (void) _receivedCloseResponse: (BLIPResponse*)response
    2.98 +{
    2.99 +    NSError *error = response.error;
   2.100 +    LogTo(BLIPVerbose,@"Received close response: error=%@",error);
   2.101 +    if( error ) {
   2.102 +        if( [_delegate respondsToSelector: @selector(connection:closeRequestFailedWithError:)] )
   2.103 +            [_delegate connection: self closeRequestFailedWithError: error];
   2.104 +    } else {
   2.105 +        // Now finally close the socket:
   2.106 +        [super _beginClose];
   2.107 +    }
   2.108 +}
   2.109 +
   2.110 +
   2.111 +- (void) _handleCloseRequest: (BLIPRequest*)request
   2.112 +{
   2.113 +    LogTo(BLIPVerbose,@"Received a close request");
   2.114 +    if( [_delegate respondsToSelector: @selector(connectionReceivedCloseRequest:)] )
   2.115 +        if( ! [_delegate connectionReceivedCloseRequest: self] ) {
   2.116 +            LogTo(BLIPVerbose,@"Responding with denial of close request");
   2.117 +            [request respondWithErrorCode: kBLIPError_Forbidden message: @"Close request denied"];
   2.118 +            return;
   2.119 +        }
   2.120 +    
   2.121 +    LogTo(BLIPVerbose,@"Close request accepted");
   2.122 +    _blipClosing = YES; // this prevents _beginClose from sending a close request back
   2.123 +    [self close];
   2.124 +}
   2.125 +
   2.126 +
   2.127  @end
   2.128  
   2.129  
   2.130  
   2.131  
   2.132 +#pragma mark -
   2.133  @implementation BLIPListener
   2.134  
   2.135  - (id) initWithPort: (UInt16)port
     3.1 --- a/BLIP/BLIPMessage.m	Thu Jun 19 10:22:19 2008 -0700
     3.2 +++ b/BLIP/BLIPMessage.m	Thu Jun 19 16:22:05 2008 -0700
     3.3 @@ -74,6 +74,8 @@
     3.4          [desc appendString: @", urgent"];
     3.5      if( _flags & kBLIP_NoReply )
     3.6          [desc appendString: @", noreply"];
     3.7 +    if( _flags & kBLIP_Meta )
     3.8 +        [desc appendString: @", META"];
     3.9      [desc appendString: @"]"];
    3.10      return desc;
    3.11  }
    3.12 @@ -103,6 +105,8 @@
    3.13          _flags &= ~flag;
    3.14  }
    3.15  
    3.16 +- (BLIPMessageFlags) _flags                 {return _flags;}
    3.17 +
    3.18  - (BOOL) compressed                         {return (_flags & kBLIP_Compressed) != 0;}
    3.19  - (BOOL) urgent                             {return (_flags & kBLIP_Urgent) != 0;}
    3.20  - (void) setCompressed: (BOOL)compressed    {[self _setFlag: kBLIP_Compressed value: compressed];}
     4.1 --- a/BLIP/BLIPReader.m	Thu Jun 19 10:22:19 2008 -0700
     4.2 +++ b/BLIP/BLIPReader.m	Thu Jun 19 16:22:05 2008 -0700
     4.3 @@ -93,7 +93,7 @@
     4.4  
     4.5  - (BOOL) isBusy
     4.6  {
     4.7 -    return _curBytesRead > 0;
     4.8 +    return _curBytesRead > 0 || _pendingRequests.count > 0 || _pendingResponses.count > 0;
     4.9  }
    4.10  
    4.11  
     5.1 --- a/BLIP/BLIPRequest.m	Thu Jun 19 10:22:19 2008 -0700
     5.2 +++ b/BLIP/BLIPRequest.m	Thu Jun 19 16:22:05 2008 -0700
     5.3 @@ -199,6 +199,8 @@
     5.4          setObj(&_mutableBody,nil);
     5.5          
     5.6          BLIPMutableProperties *errorProps = [self.properties mutableCopy];
     5.7 +        if( ! errorProps )
     5.8 +            errorProps = [[BLIPMutableProperties alloc] init];
     5.9          NSDictionary *userInfo = error.userInfo;
    5.10          for( NSString *key in userInfo ) {
    5.11              id value = $castIf(NSString,[userInfo objectForKey: key]);
    5.12 @@ -227,8 +229,12 @@
    5.13  {
    5.14      Assert(_connection,@"%@ has no connection to send over",self);
    5.15      Assert(!_sent,@"%@ was already sent",self);
    5.16 +    BLIPWriter *writer = (BLIPWriter*)_connection.writer;
    5.17 +    Assert(writer,@"%@'s connection has no writer (already closed?)",self);
    5.18      [self _encode];
    5.19 -    return (self.sent = [(BLIPWriter*)_connection.writer sendMessage: self]);
    5.20 +    BOOL sent = self.sent = [writer sendMessage: self];
    5.21 +    Assert(sent);
    5.22 +    return sent;
    5.23  }
    5.24  
    5.25  
     6.1 --- a/BLIP/BLIPTest.m	Thu Jun 19 10:22:19 2008 -0700
     6.2 +++ b/BLIP/BLIPTest.m	Thu Jun 19 16:22:05 2008 -0700
     6.3 @@ -35,6 +35,7 @@
     6.4  #define kClientUsesSSLCert          NO
     6.5  #define kListenerRequiresSSL        NO
     6.6  #define kListenerRequiresClientCert NO
     6.7 +#define kListenerCloseAfter         50
     6.8  
     6.9  
    6.10  static SecIdentityRef GetClientIdentity(void) {
    6.11 @@ -100,36 +101,38 @@
    6.12  
    6.13  - (void) sendAMessage
    6.14  {
    6.15 -    if(_pending.count<100) {
    6.16 -        Log(@"** Sending another %i messages...", kNBatchedMessages);
    6.17 -        for( int i=0; i<kNBatchedMessages; i++ ) {
    6.18 -            size_t size = random() % 32768;
    6.19 -            NSMutableData *body = [NSMutableData dataWithLength: size];
    6.20 -            UInt8 *bytes = body.mutableBytes;
    6.21 -            for( size_t i=0; i<size; i++ )
    6.22 -                bytes[i] = i % 256;
    6.23 -            
    6.24 -            BLIPRequest *q = [_conn requestWithBody: body
    6.25 -                                         properties: $dict({@"Content-Type", @"application/octet-stream"},
    6.26 -                                                           {@"User-Agent", @"BLIPConnectionTester"},
    6.27 -                                                           {@"Date", [[NSDate date] description]},
    6.28 -                                                           {@"Size",$sprintf(@"%u",size)})];
    6.29 -            Assert(q);
    6.30 -            if( kUseCompression && (random()%2==1) )
    6.31 -                q.compressed = YES;
    6.32 -            if( random()%16 > 12 )
    6.33 -                q.urgent = YES;
    6.34 -            BLIPResponse *response = [q send];
    6.35 -            Assert(response);
    6.36 -            Assert(q.number>0);
    6.37 -            Assert(response.number==q.number);
    6.38 -            [_pending setObject: $object(size) forKey: $object(q.number)];
    6.39 -            response.onComplete = $target(self,responseArrived:);
    6.40 +    if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) {
    6.41 +        if(_pending.count<100) {
    6.42 +            Log(@"** Sending another %i messages...", kNBatchedMessages);
    6.43 +            for( int i=0; i<kNBatchedMessages; i++ ) {
    6.44 +                size_t size = random() % 32768;
    6.45 +                NSMutableData *body = [NSMutableData dataWithLength: size];
    6.46 +                UInt8 *bytes = body.mutableBytes;
    6.47 +                for( size_t i=0; i<size; i++ )
    6.48 +                    bytes[i] = i % 256;
    6.49 +                
    6.50 +                BLIPRequest *q = [_conn requestWithBody: body
    6.51 +                                             properties: $dict({@"Content-Type", @"application/octet-stream"},
    6.52 +                                                               {@"User-Agent", @"BLIPConnectionTester"},
    6.53 +                                                               {@"Date", [[NSDate date] description]},
    6.54 +                                                               {@"Size",$sprintf(@"%u",size)})];
    6.55 +                Assert(q);
    6.56 +                if( kUseCompression && (random()%2==1) )
    6.57 +                    q.compressed = YES;
    6.58 +                if( random()%16 > 12 )
    6.59 +                    q.urgent = YES;
    6.60 +                BLIPResponse *response = [q send];
    6.61 +                Assert(response);
    6.62 +                Assert(q.number>0);
    6.63 +                Assert(response.number==q.number);
    6.64 +                [_pending setObject: $object(size) forKey: $object(q.number)];
    6.65 +                response.onComplete = $target(self,responseArrived:);
    6.66 +            }
    6.67 +        } else {
    6.68 +            Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
    6.69          }
    6.70 -    } else {
    6.71 -        Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
    6.72 +        [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
    6.73      }
    6.74 -    [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
    6.75  }
    6.76  
    6.77  - (void) responseArrived: (BLIPResponse*)response
    6.78 @@ -217,6 +220,7 @@
    6.79  @interface BLIPTestListener : NSObject <TCPListenerDelegate, BLIPConnectionDelegate>
    6.80  {
    6.81      BLIPListener *_listener;
    6.82 +    int _nReceived;
    6.83  }
    6.84  
    6.85  @end
    6.86 @@ -277,6 +281,7 @@
    6.87  - (void) connectionDidOpen: (TCPConnection*)connection
    6.88  {
    6.89      Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel);
    6.90 +    _nReceived = 0;
    6.91  }
    6.92  - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
    6.93  {
    6.94 @@ -312,6 +317,11 @@
    6.95      AssertEq([[request valueOfProperty: @"Size"] intValue], size);
    6.96  
    6.97      [request respondWithData: body contentType: request.contentType];
    6.98 +    
    6.99 +    if( ++ _nReceived == kListenerCloseAfter ) {
   6.100 +        Log(@"********** Closing BLIPTestListener after %i requests",_nReceived);
   6.101 +        [connection close];
   6.102 +    }
   6.103  }
   6.104  
   6.105  
     7.1 --- a/BLIP/BLIPWriter.m	Thu Jun 19 10:22:19 2008 -0700
     7.2 +++ b/BLIP/BLIPWriter.m	Thu Jun 19 16:22:05 2008 -0700
     7.3 @@ -79,10 +79,6 @@
     7.4  
     7.5  - (BOOL) sendMessage: (BLIPMessage*)message
     7.6  {
     7.7 -    if( _shouldClose ) {
     7.8 -        Warn(@"%@: Attempt to send a message after the connection has started closing",self);
     7.9 -        return NO;
    7.10 -    }
    7.11      Assert(!message.sent,@"message has already been sent");
    7.12      [self _queueMessage: message isNew: YES];
    7.13      return YES;
    7.14 @@ -91,12 +87,14 @@
    7.15  
    7.16  - (BOOL) sendRequest: (BLIPRequest*)q response: (BLIPResponse*)response
    7.17  {
    7.18 -    if( !_shouldClose ) {
    7.19 -        [q _assignedNumber: ++_numRequestsSent];
    7.20 -        if( response ) {
    7.21 -            [response _assignedNumber: _numRequestsSent];
    7.22 -            [(BLIPReader*)self.reader _addPendingResponse: response];
    7.23 -        }
    7.24 +    if( _shouldClose ) {
    7.25 +        Warn(@"%@: Attempt to send a request after the connection has started closing: %@",self,q);
    7.26 +        return NO;
    7.27 +    }
    7.28 +    [q _assignedNumber: ++_numRequestsSent];
    7.29 +    if( response ) {
    7.30 +        [response _assignedNumber: _numRequestsSent];
    7.31 +        [(BLIPReader*)self.reader _addPendingResponse: response];
    7.32      }
    7.33      return [self sendMessage: q];
    7.34  }
     8.1 --- a/BLIP/BLIP_Internal.h	Thu Jun 19 10:22:19 2008 -0700
     8.2 +++ b/BLIP/BLIP_Internal.h	Thu Jun 19 16:22:05 2008 -0700
     8.3 @@ -29,6 +29,7 @@
     8.4      kBLIP_Urgent    = 0x0020,       // please send sooner/faster
     8.5      kBLIP_NoReply   = 0x0040,       // no RPY needed
     8.6      kBLIP_MoreComing= 0x0080,       // More frames coming (Applies only to individual frame)
     8.7 +    kBLIP_Meta      = 0x0100,       // Special message type, handled internally (hello, bye, ...)
     8.8  };
     8.9  typedef UInt16 BLIPMessageFlags;
    8.10  
    8.11 @@ -43,6 +44,9 @@
    8.12  
    8.13  #define kBLIPFrameHeaderMagicNumber 0x9B34F205
    8.14  
    8.15 +#define kBLIPProfile_Hi  @"Hi"      // Used for Profile header in meta greeting message
    8.16 +#define kBLIPProfile_Bye @"Bye"     // Used for Profile header in meta close-request message
    8.17 +
    8.18  
    8.19  @interface BLIPConnection ()
    8.20  - (void) _dispatchRequest: (BLIPRequest*)request;
    8.21 @@ -52,6 +56,7 @@
    8.22  
    8.23  @interface BLIPMessage ()
    8.24  @property BOOL sent, propertiesAvailable, complete;
    8.25 +- (BLIPMessageFlags) _flags;
    8.26  - (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value;
    8.27  - (void) _encode;
    8.28  @end
     9.1 --- a/TCP/TCPConnection.h	Thu Jun 19 10:22:19 2008 -0700
     9.2 +++ b/TCP/TCPConnection.h	Thu Jun 19 16:22:05 2008 -0700
     9.3 @@ -108,6 +108,7 @@
     9.4  // protected:
     9.5  - (Class) readerClass;
     9.6  - (Class) writerClass;
     9.7 +- (void) _beginClose;
     9.8  
     9.9  @end
    9.10  
    10.1 --- a/TCP/TCPConnection.m	Thu Jun 19 10:22:19 2008 -0700
    10.2 +++ b/TCP/TCPConnection.m	Thu Jun 19 16:22:05 2008 -0700
    10.3 @@ -207,9 +207,7 @@
    10.4      if( _status > kTCP_Closed ) {
    10.5          LogTo(TCP,@"%@ disconnecting",self);
    10.6          [_writer disconnect];
    10.7 -        setObj(&_writer,nil);
    10.8          [_reader disconnect];
    10.9 -        setObj(&_reader,nil);
   10.10          self.status = kTCP_Disconnected;
   10.11      }
   10.12      [self _stopOpenTimer];
   10.13 @@ -231,8 +229,7 @@
   10.14          LogTo(TCP,@"%@ closing",self);
   10.15          self.status = kTCP_Closing;
   10.16          [self retain];
   10.17 -        [_reader close];
   10.18 -        [_writer close];
   10.19 +        [self _beginClose];
   10.20          if( ! [self _checkIfClosed] ) {
   10.21              if( timeout <= 0.0 )
   10.22                  [self disconnect];
   10.23 @@ -251,6 +248,13 @@
   10.24  }
   10.25  
   10.26  
   10.27 +- (void) _beginClose
   10.28 +{
   10.29 +    [_reader close];
   10.30 +    [_writer close];
   10.31 +}
   10.32 +
   10.33 +
   10.34  - (BOOL) _checkIfClosed
   10.35  {
   10.36      if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
   10.37 @@ -356,27 +360,16 @@
   10.38      [[self retain] autorelease];
   10.39      setObj(&_error,error);
   10.40      [_reader disconnect];
   10.41 -    setObj(&_reader,nil);
   10.42      [_writer disconnect];
   10.43 -    setObj(&_writer,nil);
   10.44      [self _closed];
   10.45  }
   10.46  
   10.47  - (void) _streamGotEOF: (TCPStream*)stream
   10.48  {
   10.49      LogTo(TCP,@"%@ got EOF on %@",self,stream);
   10.50 -    if( stream == _reader ) {
   10.51 -        setObj(&_reader,nil);
   10.52 -        // This is the expected way for he peer to initiate closing the connection.
   10.53 -        if( _status==kTCP_Open ) {
   10.54 -            [self closeWithTimeout: INFINITY];
   10.55 -            return;
   10.56 -        }
   10.57 -    } else if( stream == _writer ) {
   10.58 -        setObj(&_writer,nil);
   10.59 -    }
   10.60 -    
   10.61 +    [stream disconnect];
   10.62      if( _status == kTCP_Closing ) {
   10.63 +        [self _streamCanClose: stream];
   10.64          [self _checkIfClosed];
   10.65      } else {
   10.66          [self _stream: stream 
   10.67 @@ -385,14 +378,27 @@
   10.68  }
   10.69  
   10.70  
   10.71 +// Called as soon as a stream is ready to close, after its -close method has been called.
   10.72 +- (void) _streamCanClose: (TCPStream*)stream
   10.73 +{
   10.74 +    if( ! _reader.isActive && !_writer.isActive ) {
   10.75 +        LogTo(TCPVerbose,@"Both streams are ready to close now!");
   10.76 +        [_reader disconnect];
   10.77 +        [_writer disconnect];
   10.78 +    }
   10.79 +}
   10.80 +
   10.81 +
   10.82  // Called after I called -close on a stream and it finished closing:
   10.83 -- (void) _streamClosed: (TCPStream*)stream
   10.84 +- (void) _streamDisconnected: (TCPStream*)stream
   10.85  {
   10.86 -    LogTo(TCP,@"%@ finished closing %@",self,stream);
   10.87 +    LogTo(TCP,@"%@: disconnected %@",self,stream);
   10.88      if( stream == _reader )
   10.89          setObj(&_reader,nil);
   10.90      else if( stream == _writer )
   10.91          setObj(&_writer,nil);
   10.92 +    else
   10.93 +        return;
   10.94      if( !_reader.isOpen && !_writer.isOpen )
   10.95          [self _closed];
   10.96  }
    11.1 --- a/TCP/TCPStream.h	Thu Jun 19 10:22:19 2008 -0700
    11.2 +++ b/TCP/TCPStream.h	Thu Jun 19 16:22:05 2008 -0700
    11.3 @@ -47,6 +47,9 @@
    11.4  /** Does the stream have pending data to read or write, that prevents it from closing? */
    11.5  @property (readonly) BOOL isBusy;
    11.6  
    11.7 +/** Returns NO if the stream is ready to close (-close has been called and -isBusy is NO.) */
    11.8 +@property (readonly) BOOL isActive;
    11.9 +
   11.10  /** Generic accessor for CFStream/NSStream properties. */
   11.11  - (id) propertyForKey: (CFStringRef)cfStreamProperty;
   11.12  
    12.1 --- a/TCP/TCPStream.m	Thu Jun 19 10:22:19 2008 -0700
    12.2 +++ b/TCP/TCPStream.m	Thu Jun 19 16:22:05 2008 -0700
    12.3 @@ -110,20 +110,24 @@
    12.4          [_stream close];
    12.5          setObj(&_stream,nil);
    12.6      }
    12.7 -    setObj(&_conn,nil);
    12.8 +    if( _conn ) {
    12.9 +        [self retain];
   12.10 +        [_conn _streamDisconnected: self];
   12.11 +        setObj(&_conn,nil);
   12.12 +        [self release];
   12.13 +    }
   12.14  }
   12.15  
   12.16  
   12.17  - (BOOL) close
   12.18  {
   12.19 +    _shouldClose = YES;
   12.20      if( self.isBusy ) {
   12.21 -        _shouldClose = YES;
   12.22          return NO;
   12.23      } else {
   12.24 -        LogTo(TCP,@"Closing %@",self);
   12.25 -        [[self retain] autorelease];    // don't let myself be dealloced in the midst of this
   12.26 -        [_conn _streamClosed: self];    // have to do this before disconnect
   12.27 -        [self disconnect];
   12.28 +        LogTo(TCP,@"Request to close %@",self);
   12.29 +        [[self retain] autorelease];        // don't let myself be dealloced in the midst of this
   12.30 +        [_conn _streamCanClose: self];
   12.31          return YES;
   12.32      }
   12.33  }
   12.34 @@ -140,6 +144,11 @@
   12.35      return NO;  // abstract
   12.36  }
   12.37  
   12.38 +- (BOOL) isActive
   12.39 +{
   12.40 +    return !_shouldClose || self.isBusy;
   12.41 +}
   12.42 +
   12.43  
   12.44  - (void) _opened
   12.45  {
   12.46 @@ -158,14 +167,7 @@
   12.47  
   12.48  - (void) _gotEOF
   12.49  {
   12.50 -    if( self.isBusy )
   12.51 -        [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
   12.52 -    else {
   12.53 -        [self retain];
   12.54 -        [_conn _streamGotEOF: self];
   12.55 -        [self disconnect];
   12.56 -        [self release];
   12.57 -    }
   12.58 +    [_conn _streamGotEOF: self];
   12.59  }
   12.60  
   12.61  - (BOOL) _gotError: (NSError*)error
    13.1 --- a/TCP/TCP_Internal.h	Thu Jun 19 10:22:19 2008 -0700
    13.2 +++ b/TCP/TCP_Internal.h	Thu Jun 19 16:22:05 2008 -0700
    13.3 @@ -20,6 +20,7 @@
    13.4  - (void) _streamOpened: (TCPStream*)stream;
    13.5  - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream;
    13.6  - (void) _stream: (TCPStream*)stream gotError: (NSError*)error;
    13.7 +- (void) _streamCanClose: (TCPStream*)stream;
    13.8  - (void) _streamGotEOF: (TCPStream*)stream;
    13.9 -- (void) _streamClosed: (TCPStream*)stream;
   13.10 +- (void) _streamDisconnected: (TCPStream*)stream;
   13.11  @end