-
Notifications
You must be signed in to change notification settings - Fork 0
Fix ObjC server-mode premature TCP close and CI linker error #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -54,6 +54,11 @@ @interface GCDAsyncSocket () | |||
| // TLS | ||||
| @property (nonatomic, assign) BOOL tlsEnabled; | ||||
|
|
||||
| // Write queue tracking | ||||
| @property (nonatomic, assign) NSUInteger pendingWriteCount; | ||||
| @property (nonatomic, assign) BOOL flagDisconnectAfterWrites; | ||||
| @property (nonatomic, assign) BOOL flagDisconnectAfterReads; | ||||
|
|
||||
| @end | ||||
|
|
||||
| @implementation GCDAsyncSocket | ||||
|
|
@@ -674,11 +679,35 @@ - (void)disconnect { | |||
| } | ||||
|
|
||||
| - (void)disconnectAfterWriting { | ||||
| [self disconnect]; | ||||
| __weak typeof(self) weakSelf = self; | ||||
| dispatch_async(self.socketQueue, ^{ | ||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||
| if (!strongSelf) return; | ||||
|
|
||||
| strongSelf.flagDisconnectAfterWrites = YES; | ||||
| // If no writes are in flight, disconnect immediately. | ||||
| // Otherwise the send-completion handler will disconnect | ||||
| // once the last pending write finishes. | ||||
| if (strongSelf.pendingWriteCount == 0) { | ||||
| [strongSelf disconnectInternalWithError:nil]; | ||||
| } | ||||
| }); | ||||
| } | ||||
|
|
||||
| - (void)disconnectAfterReading { | ||||
| [self disconnect]; | ||||
| __weak typeof(self) weakSelf = self; | ||||
| dispatch_async(self.socketQueue, ^{ | ||||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||||
| if (!strongSelf) return; | ||||
|
|
||||
| strongSelf.flagDisconnectAfterReads = YES; | ||||
| // If no read requests are pending, disconnect immediately. | ||||
| // Otherwise the read-completion callback will disconnect | ||||
| // once the last pending read request is fulfilled. | ||||
| if (strongSelf.readQueue.count == 0) { | ||||
| [strongSelf disconnectInternalWithError:nil]; | ||||
| } | ||||
| }); | ||||
| } | ||||
|
|
||||
| #pragma mark - Reading | ||||
|
|
@@ -773,9 +802,15 @@ - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)t | |||
| strongSelf.socketQueue, timeoutBlock); | ||||
| } | ||||
|
|
||||
| strongSelf.pendingWriteCount++; | ||||
|
|
||||
| // is_complete must be false so the TCP stream stays open for | ||||
| // subsequent writes (e.g. HTTP header followed by body). | ||||
| // Passing true here would send a TCP FIN after each write, | ||||
| // closing the write side of the connection prematurely. | ||||
| nw_connection_send(strongSelf.connection, dispatchData, | ||||
| NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, | ||||
| true, ^(nw_error_t _Nullable error) { | ||||
| false, ^(nw_error_t _Nullable error) { | ||||
| writeCompleted = YES; | ||||
| if (timeoutBlock) { | ||||
| dispatch_block_cancel(timeoutBlock); | ||||
|
|
@@ -785,19 +820,28 @@ - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)t | |||
| __strong typeof(weakSelf) sself = weakSelf; | ||||
| if (!sself) return; | ||||
|
|
||||
| // This completion handler fires on socketQueue (set via | ||||
| // nw_connection_set_queue), so we can safely mutate state. | ||||
| sself.pendingWriteCount--; | ||||
|
|
||||
| if (error) { | ||||
| NSError *nsError = [sself socketErrorWithCode:GCDAsyncSocketErrorConnectionFailed | ||||
| description:@"Write failed." | ||||
| reason:@"nw_connection_send failed" | ||||
| nwError:error]; | ||||
| [sself disconnectWithError:nsError]; | ||||
| [sself disconnectInternalWithError:nsError]; | ||||
| } else { | ||||
| dispatch_async(sself.delegateQueue, ^{ | ||||
| id delegate = sself.delegate; | ||||
| if ([delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) { | ||||
| [delegate socket:sself didWriteDataWithTag:tag]; | ||||
| } | ||||
| }); | ||||
|
|
||||
| // Check if we should disconnect after all writes complete | ||||
| if (sself.flagDisconnectAfterWrites && sself.pendingWriteCount == 0) { | ||||
| [sself disconnectInternalWithError:nil]; | ||||
| } | ||||
| } | ||||
| }); | ||||
| }); | ||||
|
|
@@ -1013,6 +1057,11 @@ - (void)processReadQueue { | |||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
| // Check if we should disconnect after all read requests are fulfilled | ||||
| if (self.flagDisconnectAfterReads && self.readQueue.count == 0) { | ||||
| [self disconnectInternalWithError:nil]; | ||||
| } | ||||
| } | ||||
|
|
||||
| #pragma mark - Private: Disconnect | ||||
|
|
@@ -1035,6 +1084,9 @@ - (void)disconnectInternalWithError:(NSError *)error { | |||
|
|
||||
| self.isConnected = NO; | ||||
| self.isReadingContinuously = NO; | ||||
| self.flagDisconnectAfterWrites = NO; | ||||
| self.flagDisconnectAfterReads = NO; | ||||
| self.pendingWriteCount = 0; | ||||
|
||||
| self.pendingWriteCount = 0; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |||||
| // 6. GCDAsyncSocket — Server socket API (accept/listen) | ||||||
| // | ||||||
| // Build (from repository root): | ||||||
| // clang -framework Foundation \ | ||||||
| // clang -framework Foundation -framework Network -framework Security \ | ||||||
|
||||||
| // clang -framework Foundation -framework Network -framework Security \ | |
| // clang -fobjc-arc -framework Foundation -framework Network -framework Security \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pendingWriteCountis decremented unconditionally in thenw_connection_sendcompletion. IfdisconnectInternalWithError:runs while writes are in-flight (e.g., user callsdisconnect, a read error triggers disconnect, or a send error causes disconnect while other sends are still pending), it currently resetspendingWriteCountto 0 and cancels the connection; subsequent send completions can still fire and will underflow theNSUIntegercounter. Consider guarding the decrement (only decrement when > 0) and/or not resetting the counter until all in-flight completions have been accounted for (e.g., mark a disconnecting state and ignore late completions).