diff --git a/lib/std/os.zig b/lib/std/os.zig index f33e206a65..601839ea1c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2728,10 +2728,21 @@ pub const SocketError = error{ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t { if (builtin.os.tag == .windows) { - // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations + // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into + // windows-analagous operations const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC); - const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0; - const rc = try windows.WSASocketW(@bitCast(i32, domain), @bitCast(i32, filtered_sock_type), @bitCast(i32, protocol), null, 0, flags); + const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) + windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT + else + 0; + const rc = try windows.WSASocketW( + @bitCast(i32, domain), + @bitCast(i32, filtered_sock_type), + @bitCast(i32, protocol), + null, + 0, + flags, + ); errdefer windows.closesocket(rc) catch unreachable; if ((socket_type & SOCK_NONBLOCK) != 0) { var mode: c_ulong = 1; // nonblocking diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index db261c0ecc..9d898dadaf 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1261,7 +1261,7 @@ pub fn WSAStartup(majorVersion: u8, minorVersion: u8) !ws2_32.WSADATA { .WSASYSNOTREADY => return error.SystemNotAvailable, .WSAVERNOTSUPPORTED => return error.VersionNotSupported, .WSAEINPROGRESS => return error.BlockingOperationInProgress, - .WSAEPROCLIM => return error.SystemResources, + .WSAEPROCLIM => return error.ProcessFdQuotaExceeded, else => |err| return unexpectedWSAError(err), }, }; @@ -1280,6 +1280,30 @@ pub fn WSACleanup() !void { }; } +var wsa_startup_mutex: std.Thread.Mutex = .{}; + +/// Microsoft requires WSAStartup to be called to initialize, or else +/// WSASocketW will return WSANOTINITIALISED. +/// Since this is a standard library, we do not have the luxury of +/// putting initialization code anywhere, because we would not want +/// to pay the cost of calling WSAStartup if there ended up being no +/// networking. Also, if Zig code is used as a library, Zig is not in +/// charge of the start code, and we couldn't put in any initialization +/// code even if we wanted to. +/// The documentation for WSAStartup mentions that there must be a +/// matching WSACleanup call. It is not possible for the Zig Standard +/// Library to honor this for the same reason - there is nowhere to put +/// deinitialization code. +/// So, API users of the zig std lib have two options: +/// * (recommended) The simple, cross-platform way: just call `WSASocketW` +/// and don't worry about it. Zig will call WSAStartup() in a thread-safe +/// manner and never deinitialize networking. This is ideal for an +/// application which has the capability to do networking. +/// * The getting-your-hands-dirty way: call `WSAStartup()` before doing +/// networking, so that the error handling code for WSANOTINITIALISED never +/// gets run, which then allows the application or library to call `WSACleanup()`. +/// This could make sense for a library, which has init and deinit +/// functions for the whole library's lifetime. pub fn WSASocketW( af: i32, socket_type: i32, @@ -1288,17 +1312,40 @@ pub fn WSASocketW( g: ws2_32.GROUP, dwFlags: DWORD, ) !ws2_32.SOCKET { - const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags); - if (rc == ws2_32.INVALID_SOCKET) { - switch (ws2_32.WSAGetLastError()) { - .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, - .WSAEMFILE => return error.ProcessFdQuotaExceeded, - .WSAENOBUFS => return error.SystemResources, - .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported, - else => |err| return unexpectedWSAError(err), + var first = true; + while (true) { + const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags); + if (rc == ws2_32.INVALID_SOCKET) { + switch (ws2_32.WSAGetLastError()) { + .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported, + .WSAEMFILE => return error.ProcessFdQuotaExceeded, + .WSAENOBUFS => return error.SystemResources, + .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported, + .WSANOTINITIALISED => { + if (!first) return error.Unexpected; + first = false; + + var held = wsa_startup_mutex.acquire(); + defer held.release(); + + // Here we could use a flag to prevent multiple threads to prevent + // multiple calls to WSAStartup, but it doesn't matter. We're globally + // leaking the resource intentionally, and the mutex already prevents + // data races within the WSAStartup function. + _ = WSAStartup(2, 2) catch |err| switch (err) { + error.SystemNotAvailable => return error.SystemResources, + error.VersionNotSupported => return error.Unexpected, + error.BlockingOperationInProgress => return error.Unexpected, + error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded, + error.Unexpected => return error.Unexpected, + }; + continue; + }, + else => |err| return unexpectedWSAError(err), + } } + return rc; } - return rc; } pub fn bind(s: ws2_32.SOCKET, name: *const ws2_32.sockaddr, namelen: ws2_32.socklen_t) i32 {