@@ -545,13 +545,58 @@ pub const Loop = struct {
545
545
.close = > | v | .{ .result = .{ .close = windows .CloseHandle (v .fd ) } },
546
546
547
547
.connect = > | * v | action : {
548
- const result = windows .ws2_32 .connect (asSocket (v .socket ), & v .addr .any , @as (i32 , @intCast (v .addr .getOsSockLen ())));
549
- if (result != 0 ) {
550
- const err = windows .ws2_32 .WSAGetLastError ();
551
- break :action switch (err ) {
552
- else = > .{ .result = .{ .connect = windows .unexpectedWSAError (err ) } },
548
+ const as_socket = asSocket (v .socket );
549
+ // Associate our socket with loop's completion port.
550
+ self .associate_fd (v .socket ) catch unreachable ;
551
+
552
+ // ConnectEx requires socket to be initially bound.
553
+ // https://github.com/tigerbeetle/tigerbeetle/blob/main/src/io/windows.zig#L467
554
+ {
555
+ const inaddr_any = std .mem .zeroes ([4 ]u8 );
556
+ const bind_addr = std .net .Address .initIp4 (inaddr_any , 0 );
557
+ // NOTE: This may return many other errors; we should extend `ConnectError` set.
558
+ posix .bind (as_socket , & bind_addr .any , bind_addr .getOsSockLen ()) catch unreachable ;
559
+ }
560
+
561
+ // NOTE: This can be declared in somewhere else; it all happens in comptime though so no issue putting it here.
562
+ const LPFN_CONNECTEX = * const fn (
563
+ Socket : windows.ws2_32.SOCKET ,
564
+ SockAddr : * const windows.ws2_32.sockaddr ,
565
+ SockLen : posix.socklen_t ,
566
+ SendBuf : ? * const anyopaque ,
567
+ SendBufLen : windows.DWORD ,
568
+ BytesSent : * windows.DWORD ,
569
+ Overlapped : * windows.OVERLAPPED ,
570
+ ) callconv (.winapi ) windows .BOOL ;
571
+
572
+ // Dynamically load the ConnectEx function.
573
+ const ConnectEx = windows .loadWinsockExtensionFunction (LPFN_CONNECTEX , as_socket , windows .ws2_32 .WSAID_CONNECTEX ) catch | err | switch (err ) {
574
+ error .OperationNotSupported = > unreachable , // Something other than sockets has given.
575
+ error .FileDescriptorNotASocket = > unreachable , // Must be preferred on a socket.
576
+ error .ShortRead = > unreachable ,
577
+ error .Unexpected = > break :action .{ .result = .{ .connect = error .Unexpected } },
578
+ };
579
+
580
+ // Connect attempt.
581
+ var bytes_transferred : windows.DWORD = 0 ;
582
+ const result = ConnectEx (as_socket , & v .addr .any , v .addr .getOsSockLen (), null , 0 , & bytes_transferred , & completion .overlapped );
583
+
584
+ // If ConnectEx returns `windows.TRUE`, it means operation completed immediately.
585
+ // Which is most of the time not the case; we should check it anyways though!
586
+ if (result == windows .FALSE ) {
587
+ // NOTE: This may return many other errors; we should extend `ConnectError` set.
588
+ break :action switch (windows .ws2_32 .WSAGetLastError ()) {
589
+ .WSA_IO_PENDING , .WSAEWOULDBLOCK , .WSA_IO_INCOMPLETE = > .{ .submitted = {} }, // Operation will be completed in the future.
590
+ else = > | err | .{ .result = .{ .connect = windows .unexpectedWSAError (err ) } },
553
591
};
554
592
}
593
+
594
+ // Surprisingly, we connected immediately.
595
+ // The following is necessary for various functions (shutdown, getsockopt, setsockopt, getsockname, getpeername) to work.
596
+ // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex#remarks
597
+ // https://stackoverflow.com/questions/13598530/connectex-requires-the-socket-to-be-initially-bound-but-to-what
598
+ _ = windows .ws2_32 .setsockopt (asSocket (v .socket ), windows .ws2_32 .SOL .SOCKET , windows .ws2_32 .SO .UPDATE_CONNECT_CONTEXT , null , 0 );
599
+
555
600
break :action .{ .result = .{ .connect = {} } };
556
601
},
557
602
@@ -960,7 +1005,7 @@ pub const Completion = struct {
960
1005
/// operation for the completion.
961
1006
pub fn perform (self : * Completion ) Result {
962
1007
return switch (self .op ) {
963
- .noop , .close , .connect , . shutdown , .timer , .cancel = > {
1008
+ .noop , .close , .shutdown , .timer , .cancel = > {
964
1009
std .log .warn ("perform op={s}" , .{@tagName (self .op )});
965
1010
unreachable ;
966
1011
},
@@ -985,7 +1030,27 @@ pub const Completion = struct {
985
1030
return .{ .accept = self .op .accept .internal_accept_socket .? };
986
1031
},
987
1032
988
- .read = > | * v | {
1033
+ .connect = > | * v | r : {
1034
+ const as_socket = asSocket (v .socket );
1035
+ var transferred : windows.DWORD = 0 ;
1036
+ var flags : windows.DWORD = 0 ;
1037
+ const result = windows .ws2_32 .WSAGetOverlappedResult (as_socket , & self .overlapped , & transferred , windows .FALSE , & flags );
1038
+
1039
+ // Connected successfully.
1040
+ if (result == windows .TRUE ) {
1041
+ // The following is necessary for various functions (shutdown, getsockopt, setsockopt, getsockname, getpeername) to work.
1042
+ // https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex#remarks
1043
+ // https://stackoverflow.com/questions/13598530/connectex-requires-the-socket-to-be-initially-bound-but-to-what
1044
+ _ = windows .ws2_32 .setsockopt (as_socket , windows .ws2_32 .SOL .SOCKET , windows .ws2_32 .SO .UPDATE_CONNECT_CONTEXT , null , 0 );
1045
+
1046
+ break :r .{ .connect = {} };
1047
+ }
1048
+
1049
+ // We got an error.
1050
+ break :r .{ .connect = windows .unexpectedWSAError (windows .ws2_32 .WSAGetLastError ()) };
1051
+ },
1052
+
1053
+ .read = > | * v | r : {
989
1054
var bytes_transferred : windows.DWORD = 0 ;
990
1055
const result = windows .kernel32 .GetOverlappedResult (v .fd , & self .overlapped , & bytes_transferred , windows .FALSE );
991
1056
if (result == windows .FALSE ) {
0 commit comments