diff --git a/ChangeLog b/ChangeLog index 1446da7b8..dc1456f61 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2006-08-10 Richard Frith-Macdonald + + * Source\GSStream.h: + * Source\GSStream.m: + * Source\unix\NSStream.m: + * Source\win32\NSStreamWin32.m: + Ensure all events are posted. Fixup for winsock write signalling. + 2006-08-09 Richard Frith-Macdonald * Documentation/Base.gsdoc: Improve links to macros. diff --git a/Source/GSStream.h b/Source/GSStream.h index fb9f9adb2..76d99efa9 100644 --- a/Source/GSStream.h +++ b/Source/GSStream.h @@ -68,12 +68,12 @@ id _delegate; /* Delegate controls operation. */\ NSMutableDictionary *_properties; /* storage for properties */\ BOOL _delegateValid;/* whether the delegate responds*/\ - BOOL _unhandledData; /* no read/write since event */\ NSError *_lastError; /* last error occured */\ NSStreamStatus _currentStatus;/* current status */\ NSMutableArray *_modes; /* currently scheduled modes. */\ NSRunLoop *_runloop; /* currently scheduled loop. */\ - void *_loopID; /* file descriptor etc */\ + void *_loopID; /* file descriptor etc. */\ + int _events; /* Signalled events. */\ } /** @@ -124,7 +124,7 @@ IVARS - (void) _recordError; /** - * say whether there is unahdnled data for the stream. + * say whether there is unhandled data for the stream. */ - (BOOL) _unhandledData; @end diff --git a/Source/GSStream.m b/Source/GSStream.m index 22f73953d..52b94deaf 100644 --- a/Source/GSStream.m +++ b/Source/GSStream.m @@ -144,8 +144,11 @@ static RunLoopEventType typeForStream(NSStream *aStream) [_runloop removeStream: self mode: [_modes objectAtIndex: i]]; } } - _unhandledData = NO; [self _setStatus: NSStreamStatusClosed]; + /* We don't want to send any events the the delegate after the + * stream has been closed. + */ + _delegateValid = NO; } - (void) dealloc @@ -213,18 +216,19 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (void) removeFromRunLoop: (NSRunLoop *)aRunLoop forMode: (NSString *)mode { - NSAssert(_runloop == aRunLoop, - @"Attempt to remove unscheduled runloop"); - if ([_modes containsObject: mode]) + if (_runloop == aRunLoop) { - if ([self _isOpened]) + if ([_modes containsObject: mode]) { - [_runloop removeStream: self mode: mode]; - } - [_modes removeObject: mode]; - if ([_modes count] == 0) - { - DESTROY(_runloop); + if ([self _isOpened]) + { + [_runloop removeStream: self mode: mode]; + } + [_modes removeObject: mode]; + if ([_modes count] == 0) + { + DESTROY(_runloop); + } } } } @@ -256,8 +260,15 @@ static RunLoopEventType typeForStream(NSStream *aStream) { _delegate = self; } - _delegateValid - = [_delegate respondsToSelector: @selector(stream:handleEvent:)]; + if ([self streamStatus] != NSStreamStatusClosed + && [self streamStatus] != NSStreamStatusError) + { + /* We don't want to send any events the the delegate after the + * stream has been closed. + */ + _delegateValid + = [_delegate respondsToSelector: @selector(stream:handleEvent:)]; + } } - (BOOL) setProperty: (id)property forKey: (NSString *)key @@ -359,47 +370,106 @@ static RunLoopEventType typeForStream(NSStream *aStream) NSStreamStatus last = [self streamStatus]; NSStreamStatus current; - if (event == NSStreamEventHasSpaceAvailable - || event == NSStreamEventHasBytesAvailable) + if (event == NSStreamEventNone) { - /* If we have a data event, we mark the stream as having unhandled - * data (so we can refrain from triggering again) until a read or - * write operation (as approriate) has been performed. - */ - _unhandledData = YES; - _unhandledData = YES; + return; } - if (_delegateValid == YES) + else if (event == NSStreamEventOpenCompleted) { - [_delegate stream: self handleEvent: event]; - } - - while ((current = [self streamStatus]) != last) - { - last = current; - - /* If we our status changed while the handler was dealing with an - * event, we must send it the new event to let it know. - */ - if (current == NSStreamStatusAtEnd) + if ((_events & event) == 0) { + _events |= NSStreamEventOpenCompleted; if (_delegateValid == YES) { - event = NSStreamEventEndEncountered; - [_delegate stream: self handleEvent: event]; + [_delegate stream: self + handleEvent: NSStreamEventOpenCompleted]; } } + } + else if (event == NSStreamEventHasBytesAvailable) + { + if ((_events & NSStreamEventOpenCompleted) == 0) + { + _events |= NSStreamEventOpenCompleted; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventOpenCompleted]; + } + } + if ((_events & NSStreamEventHasBytesAvailable) == 0) + { + _events |= NSStreamEventHasBytesAvailable; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventHasBytesAvailable]; + } + } + } + else if (event == NSStreamEventHasSpaceAvailable) + { + if ((_events & NSStreamEventOpenCompleted) == 0) + { + _events |= NSStreamEventOpenCompleted; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventOpenCompleted]; + } + } + if ((_events & NSStreamEventHasSpaceAvailable) == 0) + { + _events |= NSStreamEventHasSpaceAvailable; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventHasSpaceAvailable]; + } + } + } + else if (event == NSStreamEventErrorOccurred) + { + if ((_events & NSStreamEventErrorOccurred) == 0) + { + _events |= NSStreamEventErrorOccurred; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventErrorOccurred]; + } + } + } + else if (event == NSStreamEventEndEncountered) + { + if ((_events & NSStreamEventEndEncountered) == 0) + { + _events |= NSStreamEventEndEncountered; + if (_delegateValid == YES) + { + [_delegate stream: self + handleEvent: NSStreamEventEndEncountered]; + } + } + } + else + { + [NSException raise: NSInvalidArgumentException + format: @"Unknown event (%d) passed to _sendEvent:", event]; + } + + /* If our status changed while the handler was dealing with an + * event, we may need to send it the new event to let it know. + */ + if ((current = [self streamStatus]) != last) + { + if (current == NSStreamStatusAtEnd) + { + [self _sendEvent: NSStreamEventEndEncountered]; + } else if (current == NSStreamStatusError) { - if (_delegateValid == YES) - { - event = NSStreamEventErrorOccurred; - [_delegate stream: self handleEvent: event]; - } - } - else - { - return; // not an event. + [self _sendEvent: NSStreamEventErrorOccurred]; } } } @@ -421,12 +491,17 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (BOOL) _unhandledData { - return _unhandledData; + if (_events + & (NSStreamEventHasBytesAvailable | NSStreamEventHasSpaceAvailable)) + { + return YES; + } + return NO; } - (BOOL) runLoopShouldBlock: (BOOL*)trigger { - if (_unhandledData == YES + if ([self _unhandledData] == YES || _currentStatus == NSStreamStatusError || _currentStatus == NSStreamStatusAtEnd) { @@ -512,7 +587,7 @@ static RunLoopEventType typeForStream(NSStream *aStream) unsigned long dataSize = [_data length]; unsigned long copySize; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; NSAssert(dataSize >= _pointer, @"Buffer overflow!"); if (len + _pointer > dataSize) { @@ -602,7 +677,7 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (int) write: (const uint8_t *)buffer maxLength: (unsigned int)len { - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; if (_fixedSize) { unsigned long dataLen = [_data length]; diff --git a/Source/unix/NSStream.m b/Source/unix/NSStream.m index bc33c1e19..b4dbfea2f 100644 --- a/Source/unix/NSStream.m +++ b/Source/unix/NSStream.m @@ -297,7 +297,7 @@ static void setNonblocking(int fd) { int readLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; readLen = read((intptr_t)_loopID, buffer, len); if (readLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -475,7 +475,7 @@ static void setNonblocking(int fd) { int readLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; readLen = read((intptr_t)_loopID, buffer, len); if (readLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -676,7 +676,7 @@ static void setNonblocking(int fd) { int writeLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; writeLen = write((intptr_t)_loopID, buffer, len); if (writeLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -790,7 +790,7 @@ static void setNonblocking(int fd) { int writeLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; writeLen = write((intptr_t)_loopID, buffer, len); if (writeLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -1386,7 +1386,7 @@ static void setNonblocking(int fd) socklen_t len = [ins sockLen]; int acceptReturn = accept((intptr_t)_loopID, [ins peerAddr], &len); - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; if (acceptReturn < 0) { // test for real error if (errno != EWOULDBLOCK diff --git a/Source/win32/NSStreamWin32.m b/Source/win32/NSStreamWin32.m index 36ff24bf3..d9f220326 100644 --- a/Source/win32/NSStreamWin32.m +++ b/Source/win32/NSStreamWin32.m @@ -330,7 +330,7 @@ static void setNonblocking(SOCKET fd) { DWORD readLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; if (ReadFile((HANDLE)_loopID, buffer, len, &readLen, NULL) == 0) { [self _recordError]; @@ -509,7 +509,7 @@ static void setNonblocking(SOCKET fd) { NSStreamStatus myStatus = [self streamStatus]; - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; if (myStatus == NSStreamStatusReading) { myStatus = [self _check]; @@ -595,7 +595,7 @@ static void setNonblocking(SOCKET fd) { NSStreamStatus myStatus = [self streamStatus]; - if (_unhandledData == YES || myStatus == NSStreamStatusError) + if ([self _unhandledData] == YES || myStatus == NSStreamStatusError) { *trigger = NO; return NO; @@ -735,7 +735,7 @@ static void setNonblocking(SOCKET fd) { int readLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; readLen = recv(_sock, buffer, len, 0); if (readLen == SOCKET_ERROR) { @@ -847,18 +847,25 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { if (events.lNetworkEvents & FD_WRITE) { + NSAssert([_sibling _isOpened], NSInternalInconsistencyException); + /* Clear NSStreamStatusWriting if it was set */ [_sibling _setStatus: NSStreamStatusOpen]; - while ([_sibling hasSpaceAvailable] - && [_sibling _unhandledData] == NO) - { - [_sibling _sendEvent: NSStreamEventHasSpaceAvailable]; - } } + /* On winsock a socket is always writable unless it has had + * failure/closure or a write blocked and we have not been + * signalled again. + */ + while ([_sibling _unhandledData] == NO + && [_sibling hasSpaceAvailable]) + { + [_sibling _sendEvent: NSStreamEventHasSpaceAvailable]; + } + if (events.lNetworkEvents & FD_READ) { [self _setStatus: NSStreamStatusOpen]; while ([self hasBytesAvailable] - && _unhandledData == NO) + && [self _unhandledData] == NO) { [self _sendEvent: NSStreamEventHasBytesAvailable]; } @@ -871,7 +878,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); [_sibling _sendEvent: NSStreamEventEndEncountered]; } while ([self hasBytesAvailable] - && _unhandledData == NO) + && [self _unhandledData] == NO) { [self _sendEvent: NSStreamEventHasBytesAvailable]; } @@ -1007,7 +1014,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { DWORD writeLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; if (_shouldAppend == YES) { SetFilePointer((HANDLE)_loopID, 0, 0, FILE_END); @@ -1122,7 +1129,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { NSStreamStatus myStatus = [self streamStatus]; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; if (len < 0) { return -1; @@ -1214,7 +1221,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { NSStreamStatus myStatus = [self streamStatus]; - if (_unhandledData == YES || myStatus == NSStreamStatusError) + if ([self _unhandledData] == YES || myStatus == NSStreamStatusError) { *trigger = NO; return NO; @@ -1285,7 +1292,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { int writeLen; - _unhandledData = NO; + _events &= ~NSStreamEventHasSpaceAvailable; writeLen = send(_sock, buffer, len, 0); if (writeLen == SOCKET_ERROR) { @@ -1466,13 +1473,19 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); { if (events.lNetworkEvents & FD_WRITE) { + /* Clear NSStreamStatusWriting if it was set */ [self _setStatus: NSStreamStatusOpen]; - while ([self hasSpaceAvailable] - && _unhandledData == NO) - { - [self _sendEvent: NSStreamEventHasSpaceAvailable]; - } } + + /* On winsock a socket is always writable unless it has had + * failure/closure or a write blocked and we have not been + * signalled again. + */ + while ([self _unhandledData] == NO && [self hasSpaceAvailable]) + { + [self _sendEvent: NSStreamEventHasSpaceAvailable]; + } + if (events.lNetworkEvents & FD_READ) { [_sibling _setStatus: NSStreamStatusOpen]; @@ -1504,6 +1517,14 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); - (BOOL) runLoopShouldBlock: (BOOL*)trigger { *trigger = YES; + if ([self _unhandledData] == NO && [self streamStatus] == NSStreamStatusOpen) + { + /* In winsock, a writable status is only signalled if an earlier + * write failed (because it would block), so we must simulate the + * writable event by having the run loop trigger without blocking. + */ + return NO; + } return YES; } @end @@ -1949,7 +1970,7 @@ else NSLog(@"EVENTS 0x%x on %p", events.lNetworkEvents, self); socklen_t len = [ins sockLen]; int acceptReturn = accept(_sock, [ins peerAddr], &len); - _unhandledData = NO; + _events &= ~NSStreamEventHasBytesAvailable; if (acceptReturn == INVALID_SOCKET) { errno = WSAGetLastError();// test for real error