# HG changeset patch # User Jens Alfke # Date 1213917725 25200 # Node ID 3be241de163028d5f4718ffac9a8d584c4322dd7 # Parent 70590cc555aa1d50bede59b37f7146008e30a849 Implemented new close protocol with 'bye' meta-message. diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPConnection.h --- a/BLIP/BLIPConnection.h Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPConnection.h Thu Jun 19 16:22:05 2008 -0700 @@ -20,6 +20,7 @@ @interface BLIPConnection : TCPConnection { BLIPDispatcher *_dispatcher; + BOOL _blipClosing; } /** The delegate object that will be called when the connection opens, closes or receives messages. */ @@ -73,6 +74,13 @@ /** Called when a BLIPResponse (to one of your requests) is received from the peer. This is called after the response object's onComplete target, if any, is invoked.*/ - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response; + +/** Called when the peer wants to close the connection. Return YES to allow, NO to prevent. */ +- (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection; + +/** Called if the peer refuses a close request. + The typical error is BLIP error kBLIPError_Forbidden. */ +- (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error; @end diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPConnection.m --- a/BLIP/BLIPConnection.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPConnection.m Thu Jun 19 16:22:05 2008 -0700 @@ -15,6 +15,7 @@ #import "Logging.h" #import "Test.h" #import "ExceptionUtils.h" +#import "Target.h" NSString* const BLIPErrorDomain = @"BLIP"; @@ -33,10 +34,14 @@ } +@interface BLIPConnection () +- (void) _handleCloseRequest: (BLIPRequest*)request; +@end @implementation BLIPConnection + - (void) dealloc { [_dispatcher release]; @@ -48,6 +53,11 @@ - (id) delegate {return (id)_delegate;} - (void) setDelegate: (id)delegate {_delegate = delegate;} + +#pragma mark - +#pragma mark RECEIVING: + + - (BLIPDispatcher*) dispatcher { if( ! _dispatcher ) { @@ -58,11 +68,23 @@ } +- (void) _dispatchMetaRequest: (BLIPRequest*)request +{ + NSString* profile = request.profile; + if( [profile isEqualToString: kBLIPProfile_Bye] ) + [self _handleCloseRequest: request]; + else + [request respondWithErrorCode: kBLIPError_NotFound message: @"Unknown meta profile"]; +} + + - (void) _dispatchRequest: (BLIPRequest*)request { LogTo(BLIP,@"Received all of %@",request.descriptionWithProperties); @try{ - if( ! [self.dispatcher dispatchMessage: request] ) + if( request._flags & kBLIP_Meta ) + [self _dispatchMetaRequest: request]; + else if( ! [self.dispatcher dispatchMessage: request] ) [self tellDelegate: @selector(connection:receivedRequest:) withObject: request]; if( ! request.noReply && ! request.repliedTo ) { LogTo(BLIP,@"Returning default empty response to %@",request); @@ -81,6 +103,10 @@ } +#pragma mark - +#pragma mark SENDING: + + - (BLIPRequest*) request { return [[[BLIPRequest alloc] _initWithConnection: self body: nil properties: nil] autorelease]; @@ -103,11 +129,61 @@ } +#pragma mark - +#pragma mark CLOSING: + + +- (void) _beginClose +{ + // Override of TCPConnection method. Instead of closing the socket, send a 'bye' request: + if( ! _blipClosing ) { + LogTo(BLIPVerbose,@"Sending close request..."); + BLIPRequest *r = [self request]; + [r _setFlag: kBLIP_Meta value: YES]; + r.profile = kBLIPProfile_Bye; + BLIPResponse *response = [r send]; + response.onComplete = $target(self,_receivedCloseResponse:); + } + // Put the writer in close mode, to prevent client from sending any more requests: + [self.writer close]; +} + +- (void) _receivedCloseResponse: (BLIPResponse*)response +{ + NSError *error = response.error; + LogTo(BLIPVerbose,@"Received close response: error=%@",error); + if( error ) { + if( [_delegate respondsToSelector: @selector(connection:closeRequestFailedWithError:)] ) + [_delegate connection: self closeRequestFailedWithError: error]; + } else { + // Now finally close the socket: + [super _beginClose]; + } +} + + +- (void) _handleCloseRequest: (BLIPRequest*)request +{ + LogTo(BLIPVerbose,@"Received a close request"); + if( [_delegate respondsToSelector: @selector(connectionReceivedCloseRequest:)] ) + if( ! [_delegate connectionReceivedCloseRequest: self] ) { + LogTo(BLIPVerbose,@"Responding with denial of close request"); + [request respondWithErrorCode: kBLIPError_Forbidden message: @"Close request denied"]; + return; + } + + LogTo(BLIPVerbose,@"Close request accepted"); + _blipClosing = YES; // this prevents _beginClose from sending a close request back + [self close]; +} + + @end +#pragma mark - @implementation BLIPListener - (id) initWithPort: (UInt16)port diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPMessage.m --- a/BLIP/BLIPMessage.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPMessage.m Thu Jun 19 16:22:05 2008 -0700 @@ -74,6 +74,8 @@ [desc appendString: @", urgent"]; if( _flags & kBLIP_NoReply ) [desc appendString: @", noreply"]; + if( _flags & kBLIP_Meta ) + [desc appendString: @", META"]; [desc appendString: @"]"]; return desc; } @@ -103,6 +105,8 @@ _flags &= ~flag; } +- (BLIPMessageFlags) _flags {return _flags;} + - (BOOL) compressed {return (_flags & kBLIP_Compressed) != 0;} - (BOOL) urgent {return (_flags & kBLIP_Urgent) != 0;} - (void) setCompressed: (BOOL)compressed {[self _setFlag: kBLIP_Compressed value: compressed];} diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPReader.m --- a/BLIP/BLIPReader.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPReader.m Thu Jun 19 16:22:05 2008 -0700 @@ -93,7 +93,7 @@ - (BOOL) isBusy { - return _curBytesRead > 0; + return _curBytesRead > 0 || _pendingRequests.count > 0 || _pendingResponses.count > 0; } diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPRequest.m --- a/BLIP/BLIPRequest.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPRequest.m Thu Jun 19 16:22:05 2008 -0700 @@ -199,6 +199,8 @@ setObj(&_mutableBody,nil); BLIPMutableProperties *errorProps = [self.properties mutableCopy]; + if( ! errorProps ) + errorProps = [[BLIPMutableProperties alloc] init]; NSDictionary *userInfo = error.userInfo; for( NSString *key in userInfo ) { id value = $castIf(NSString,[userInfo objectForKey: key]); @@ -227,8 +229,12 @@ { Assert(_connection,@"%@ has no connection to send over",self); Assert(!_sent,@"%@ was already sent",self); + BLIPWriter *writer = (BLIPWriter*)_connection.writer; + Assert(writer,@"%@'s connection has no writer (already closed?)",self); [self _encode]; - return (self.sent = [(BLIPWriter*)_connection.writer sendMessage: self]); + BOOL sent = self.sent = [writer sendMessage: self]; + Assert(sent); + return sent; } diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPTest.m --- a/BLIP/BLIPTest.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPTest.m Thu Jun 19 16:22:05 2008 -0700 @@ -35,6 +35,7 @@ #define kClientUsesSSLCert NO #define kListenerRequiresSSL NO #define kListenerRequiresClientCert NO +#define kListenerCloseAfter 50 static SecIdentityRef GetClientIdentity(void) { @@ -100,36 +101,38 @@ - (void) sendAMessage { - if(_pending.count<100) { - Log(@"** Sending another %i messages...", kNBatchedMessages); - for( int i=0; i 12 ) - q.urgent = YES; - BLIPResponse *response = [q send]; - Assert(response); - Assert(q.number>0); - Assert(response.number==q.number); - [_pending setObject: $object(size) forKey: $object(q.number)]; - response.onComplete = $target(self,responseArrived:); + if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) { + if(_pending.count<100) { + Log(@"** Sending another %i messages...", kNBatchedMessages); + for( int i=0; i 12 ) + q.urgent = YES; + BLIPResponse *response = [q send]; + Assert(response); + Assert(q.number>0); + Assert(response.number==q.number); + [_pending setObject: $object(size) forKey: $object(q.number)]; + response.onComplete = $target(self,responseArrived:); + } + } else { + Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count); } - } else { - Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count); + [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval]; } - [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval]; } - (void) responseArrived: (BLIPResponse*)response @@ -217,6 +220,7 @@ @interface BLIPTestListener : NSObject { BLIPListener *_listener; + int _nReceived; } @end @@ -277,6 +281,7 @@ - (void) connectionDidOpen: (TCPConnection*)connection { Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel); + _nReceived = 0; } - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert { @@ -312,6 +317,11 @@ AssertEq([[request valueOfProperty: @"Size"] intValue], size); [request respondWithData: body contentType: request.contentType]; + + if( ++ _nReceived == kListenerCloseAfter ) { + Log(@"********** Closing BLIPTestListener after %i requests",_nReceived); + [connection close]; + } } diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIPWriter.m --- a/BLIP/BLIPWriter.m Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIPWriter.m Thu Jun 19 16:22:05 2008 -0700 @@ -79,10 +79,6 @@ - (BOOL) sendMessage: (BLIPMessage*)message { - if( _shouldClose ) { - Warn(@"%@: Attempt to send a message after the connection has started closing",self); - return NO; - } Assert(!message.sent,@"message has already been sent"); [self _queueMessage: message isNew: YES]; return YES; @@ -91,12 +87,14 @@ - (BOOL) sendRequest: (BLIPRequest*)q response: (BLIPResponse*)response { - if( !_shouldClose ) { - [q _assignedNumber: ++_numRequestsSent]; - if( response ) { - [response _assignedNumber: _numRequestsSent]; - [(BLIPReader*)self.reader _addPendingResponse: response]; - } + if( _shouldClose ) { + Warn(@"%@: Attempt to send a request after the connection has started closing: %@",self,q); + return NO; + } + [q _assignedNumber: ++_numRequestsSent]; + if( response ) { + [response _assignedNumber: _numRequestsSent]; + [(BLIPReader*)self.reader _addPendingResponse: response]; } return [self sendMessage: q]; } diff -r 70590cc555aa -r 3be241de1630 BLIP/BLIP_Internal.h --- a/BLIP/BLIP_Internal.h Thu Jun 19 10:22:19 2008 -0700 +++ b/BLIP/BLIP_Internal.h Thu Jun 19 16:22:05 2008 -0700 @@ -29,6 +29,7 @@ kBLIP_Urgent = 0x0020, // please send sooner/faster kBLIP_NoReply = 0x0040, // no RPY needed kBLIP_MoreComing= 0x0080, // More frames coming (Applies only to individual frame) + kBLIP_Meta = 0x0100, // Special message type, handled internally (hello, bye, ...) }; typedef UInt16 BLIPMessageFlags; @@ -43,6 +44,9 @@ #define kBLIPFrameHeaderMagicNumber 0x9B34F205 +#define kBLIPProfile_Hi @"Hi" // Used for Profile header in meta greeting message +#define kBLIPProfile_Bye @"Bye" // Used for Profile header in meta close-request message + @interface BLIPConnection () - (void) _dispatchRequest: (BLIPRequest*)request; @@ -52,6 +56,7 @@ @interface BLIPMessage () @property BOOL sent, propertiesAvailable, complete; +- (BLIPMessageFlags) _flags; - (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value; - (void) _encode; @end diff -r 70590cc555aa -r 3be241de1630 TCP/TCPConnection.h --- a/TCP/TCPConnection.h Thu Jun 19 10:22:19 2008 -0700 +++ b/TCP/TCPConnection.h Thu Jun 19 16:22:05 2008 -0700 @@ -108,6 +108,7 @@ // protected: - (Class) readerClass; - (Class) writerClass; +- (void) _beginClose; @end diff -r 70590cc555aa -r 3be241de1630 TCP/TCPConnection.m --- a/TCP/TCPConnection.m Thu Jun 19 10:22:19 2008 -0700 +++ b/TCP/TCPConnection.m Thu Jun 19 16:22:05 2008 -0700 @@ -207,9 +207,7 @@ if( _status > kTCP_Closed ) { LogTo(TCP,@"%@ disconnecting",self); [_writer disconnect]; - setObj(&_writer,nil); [_reader disconnect]; - setObj(&_reader,nil); self.status = kTCP_Disconnected; } [self _stopOpenTimer]; @@ -231,8 +229,7 @@ LogTo(TCP,@"%@ closing",self); self.status = kTCP_Closing; [self retain]; - [_reader close]; - [_writer close]; + [self _beginClose]; if( ! [self _checkIfClosed] ) { if( timeout <= 0.0 ) [self disconnect]; @@ -251,6 +248,13 @@ } +- (void) _beginClose +{ + [_reader close]; + [_writer close]; +} + + - (BOOL) _checkIfClosed { if( _status == kTCP_Closing && _writer==nil && _reader==nil ) { @@ -356,27 +360,16 @@ [[self retain] autorelease]; setObj(&_error,error); [_reader disconnect]; - setObj(&_reader,nil); [_writer disconnect]; - setObj(&_writer,nil); [self _closed]; } - (void) _streamGotEOF: (TCPStream*)stream { LogTo(TCP,@"%@ got EOF on %@",self,stream); - if( stream == _reader ) { - setObj(&_reader,nil); - // This is the expected way for he peer to initiate closing the connection. - if( _status==kTCP_Open ) { - [self closeWithTimeout: INFINITY]; - return; - } - } else if( stream == _writer ) { - setObj(&_writer,nil); - } - + [stream disconnect]; if( _status == kTCP_Closing ) { + [self _streamCanClose: stream]; [self _checkIfClosed]; } else { [self _stream: stream @@ -385,14 +378,27 @@ } +// Called as soon as a stream is ready to close, after its -close method has been called. +- (void) _streamCanClose: (TCPStream*)stream +{ + if( ! _reader.isActive && !_writer.isActive ) { + LogTo(TCPVerbose,@"Both streams are ready to close now!"); + [_reader disconnect]; + [_writer disconnect]; + } +} + + // Called after I called -close on a stream and it finished closing: -- (void) _streamClosed: (TCPStream*)stream +- (void) _streamDisconnected: (TCPStream*)stream { - LogTo(TCP,@"%@ finished closing %@",self,stream); + LogTo(TCP,@"%@: disconnected %@",self,stream); if( stream == _reader ) setObj(&_reader,nil); else if( stream == _writer ) setObj(&_writer,nil); + else + return; if( !_reader.isOpen && !_writer.isOpen ) [self _closed]; } diff -r 70590cc555aa -r 3be241de1630 TCP/TCPStream.h --- a/TCP/TCPStream.h Thu Jun 19 10:22:19 2008 -0700 +++ b/TCP/TCPStream.h Thu Jun 19 16:22:05 2008 -0700 @@ -47,6 +47,9 @@ /** Does the stream have pending data to read or write, that prevents it from closing? */ @property (readonly) BOOL isBusy; +/** Returns NO if the stream is ready to close (-close has been called and -isBusy is NO.) */ +@property (readonly) BOOL isActive; + /** Generic accessor for CFStream/NSStream properties. */ - (id) propertyForKey: (CFStringRef)cfStreamProperty; diff -r 70590cc555aa -r 3be241de1630 TCP/TCPStream.m --- a/TCP/TCPStream.m Thu Jun 19 10:22:19 2008 -0700 +++ b/TCP/TCPStream.m Thu Jun 19 16:22:05 2008 -0700 @@ -110,20 +110,24 @@ [_stream close]; setObj(&_stream,nil); } - setObj(&_conn,nil); + if( _conn ) { + [self retain]; + [_conn _streamDisconnected: self]; + setObj(&_conn,nil); + [self release]; + } } - (BOOL) close { + _shouldClose = YES; if( self.isBusy ) { - _shouldClose = YES; return NO; } else { - LogTo(TCP,@"Closing %@",self); - [[self retain] autorelease]; // don't let myself be dealloced in the midst of this - [_conn _streamClosed: self]; // have to do this before disconnect - [self disconnect]; + LogTo(TCP,@"Request to close %@",self); + [[self retain] autorelease]; // don't let myself be dealloced in the midst of this + [_conn _streamCanClose: self]; return YES; } } @@ -140,6 +144,11 @@ return NO; // abstract } +- (BOOL) isActive +{ + return !_shouldClose || self.isBusy; +} + - (void) _opened { @@ -158,14 +167,7 @@ - (void) _gotEOF { - if( self.isBusy ) - [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]]; - else { - [self retain]; - [_conn _streamGotEOF: self]; - [self disconnect]; - [self release]; - } + [_conn _streamGotEOF: self]; } - (BOOL) _gotError: (NSError*)error diff -r 70590cc555aa -r 3be241de1630 TCP/TCP_Internal.h --- a/TCP/TCP_Internal.h Thu Jun 19 10:22:19 2008 -0700 +++ b/TCP/TCP_Internal.h Thu Jun 19 16:22:05 2008 -0700 @@ -20,6 +20,7 @@ - (void) _streamOpened: (TCPStream*)stream; - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream; - (void) _stream: (TCPStream*)stream gotError: (NSError*)error; +- (void) _streamCanClose: (TCPStream*)stream; - (void) _streamGotEOF: (TCPStream*)stream; -- (void) _streamClosed: (TCPStream*)stream; +- (void) _streamDisconnected: (TCPStream*)stream; @end