Skip to content

Commit dc64ad8

Browse files
authored
Merge pull request #8 from dustturtle/copilot/fix-gcdasyncsocket-crash
Fix ObjC server-mode premature TCP close and CI linker error
2 parents 2a3b527 + 33763eb commit dc64ad8

4 files changed

Lines changed: 59 additions & 7 deletions

File tree

.github/workflows/demo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Build ObjC Demo
3434
run: |
35-
clang -fobjc-arc -framework Foundation -framework Network \
35+
clang -fobjc-arc -framework Foundation -framework Network -framework Security \
3636
-I ObjC/NWAsyncSocketObjC/include \
3737
ObjC/NWAsyncSocketObjC/NWStreamBuffer.m \
3838
ObjC/NWAsyncSocketObjC/NWSSEParser.m \

ObjC/NWAsyncSocketObjC/GCDAsyncSocket.m

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ @interface GCDAsyncSocket ()
5454
// TLS
5555
@property (nonatomic, assign) BOOL tlsEnabled;
5656

57+
// Write queue tracking
58+
@property (nonatomic, assign) NSUInteger pendingWriteCount;
59+
@property (nonatomic, assign) BOOL flagDisconnectAfterWrites;
60+
@property (nonatomic, assign) BOOL flagDisconnectAfterReads;
61+
5762
@end
5863

5964
@implementation GCDAsyncSocket
@@ -674,11 +679,35 @@ - (void)disconnect {
674679
}
675680

676681
- (void)disconnectAfterWriting {
677-
[self disconnect];
682+
__weak typeof(self) weakSelf = self;
683+
dispatch_async(self.socketQueue, ^{
684+
__strong typeof(weakSelf) strongSelf = weakSelf;
685+
if (!strongSelf) return;
686+
687+
strongSelf.flagDisconnectAfterWrites = YES;
688+
// If no writes are in flight, disconnect immediately.
689+
// Otherwise the send-completion handler will disconnect
690+
// once the last pending write finishes.
691+
if (strongSelf.pendingWriteCount == 0) {
692+
[strongSelf disconnectInternalWithError:nil];
693+
}
694+
});
678695
}
679696

680697
- (void)disconnectAfterReading {
681-
[self disconnect];
698+
__weak typeof(self) weakSelf = self;
699+
dispatch_async(self.socketQueue, ^{
700+
__strong typeof(weakSelf) strongSelf = weakSelf;
701+
if (!strongSelf) return;
702+
703+
strongSelf.flagDisconnectAfterReads = YES;
704+
// If no read requests are pending, disconnect immediately.
705+
// Otherwise the read-completion callback will disconnect
706+
// once the last pending read request is fulfilled.
707+
if (strongSelf.readQueue.count == 0) {
708+
[strongSelf disconnectInternalWithError:nil];
709+
}
710+
});
682711
}
683712

684713
#pragma mark - Reading
@@ -773,9 +802,15 @@ - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)t
773802
strongSelf.socketQueue, timeoutBlock);
774803
}
775804

805+
strongSelf.pendingWriteCount++;
806+
807+
// is_complete must be false so the TCP stream stays open for
808+
// subsequent writes (e.g. HTTP header followed by body).
809+
// Passing true here would send a TCP FIN after each write,
810+
// closing the write side of the connection prematurely.
776811
nw_connection_send(strongSelf.connection, dispatchData,
777812
NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT,
778-
true, ^(nw_error_t _Nullable error) {
813+
false, ^(nw_error_t _Nullable error) {
779814
writeCompleted = YES;
780815
if (timeoutBlock) {
781816
dispatch_block_cancel(timeoutBlock);
@@ -785,19 +820,28 @@ - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)t
785820
__strong typeof(weakSelf) sself = weakSelf;
786821
if (!sself) return;
787822

823+
// This completion handler fires on socketQueue (set via
824+
// nw_connection_set_queue), so we can safely mutate state.
825+
sself.pendingWriteCount--;
826+
788827
if (error) {
789828
NSError *nsError = [sself socketErrorWithCode:GCDAsyncSocketErrorConnectionFailed
790829
description:@"Write failed."
791830
reason:@"nw_connection_send failed"
792831
nwError:error];
793-
[sself disconnectWithError:nsError];
832+
[sself disconnectInternalWithError:nsError];
794833
} else {
795834
dispatch_async(sself.delegateQueue, ^{
796835
id delegate = sself.delegate;
797836
if ([delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) {
798837
[delegate socket:sself didWriteDataWithTag:tag];
799838
}
800839
});
840+
841+
// Check if we should disconnect after all writes complete
842+
if (sself.flagDisconnectAfterWrites && sself.pendingWriteCount == 0) {
843+
[sself disconnectInternalWithError:nil];
844+
}
801845
}
802846
});
803847
});
@@ -1013,6 +1057,11 @@ - (void)processReadQueue {
10131057
}
10141058
}
10151059
}
1060+
1061+
// Check if we should disconnect after all read requests are fulfilled
1062+
if (self.flagDisconnectAfterReads && self.readQueue.count == 0) {
1063+
[self disconnectInternalWithError:nil];
1064+
}
10161065
}
10171066

10181067
#pragma mark - Private: Disconnect
@@ -1035,6 +1084,9 @@ - (void)disconnectInternalWithError:(NSError *)error {
10351084

10361085
self.isConnected = NO;
10371086
self.isReadingContinuously = NO;
1087+
self.flagDisconnectAfterWrites = NO;
1088+
self.flagDisconnectAfterReads = NO;
1089+
self.pendingWriteCount = 0;
10381090

10391091
#if NW_FRAMEWORK_AVAILABLE
10401092
if (self.listener) {

ObjC/ObjCDemo/main.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// 6. GCDAsyncSocket — Server socket API (accept/listen)
1414
//
1515
// Build (from repository root):
16-
// clang -framework Foundation \
16+
// clang -framework Foundation -framework Network -framework Security \
1717
// -I ObjC/NWAsyncSocketObjC/include \
1818
// ObjC/NWAsyncSocketObjC/NWStreamBuffer.m \
1919
// ObjC/NWAsyncSocketObjC/NWSSEParser.m \

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ The demo menu lets you test each component individually or run all at once:
321321
Build the ObjC CLI demo on macOS:
322322

323323
```bash
324-
clang -framework Foundation \
324+
clang -fobjc-arc -framework Foundation -framework Network -framework Security \
325325
-I ObjC/NWAsyncSocketObjC/include \
326326
ObjC/NWAsyncSocketObjC/NWStreamBuffer.m \
327327
ObjC/NWAsyncSocketObjC/NWSSEParser.m \

0 commit comments

Comments
 (0)