authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-20 12:24:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-20 12:28:30-07:00
log61850f88835bfc71682f9c57c28c7e64080f1389
treefea6cc389f253380903695015f7638ca9d94db9d
parent85c2ffc9ba3153fbc5d295b2bc698b5bf0344d0e

std: Windows: WSASocketW ensures WSAStartup

When WSASocketW gets WSANOTINITIALISED, now it will lock a mutex to safely call WSAStartup and then try again one time. This implementation: * Does not use recursion * Contains a detailed doc comment explaining why things are how they are * Is careful about which errors are surfaced in the respective error sets. `std.os.socket` intentionally does not have "not initialised" as one of the possible errors.

2 files changed, 71 insertions(+), 29 deletions(-)

lib/std/os.zig+14-18
...@@ -2724,29 +2724,25 @@ pub const SocketError = error{...@@ -2724,29 +2724,25 @@ pub const SocketError = error{
27242724
2725 /// The socket type is not supported by the protocol.2725 /// The socket type is not supported by the protocol.
2726 SocketTypeNotSupported,2726 SocketTypeNotSupported,
2727
2728 /// The environment for socket control has not been initialised.
2729 NotInitialised,
2730} || UnexpectedError;2727} || UnexpectedError;
27312728
2732pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {2729pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
2733 if (builtin.os.tag == .windows) {2730 if (builtin.os.tag == .windows) {
2734 // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations2731 // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into
2732 // windows-analagous operations
2735 const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);2733 const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);
2736 const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;2734 const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0)
2737 const rc = windows.WSASocketW(@bitCast(i32, domain), @bitCast(i32, filtered_sock_type), @bitCast(i32, protocol), null, 0, flags) catch |err| switch (err) {2735 windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT
2738 error.NotInitialised => again: {2736 else
2739 // Before a socket is made Windows requires WSAStartup to be called.2737 0;
2740 // Let's try doing that now, then make the socket again. If socket creation still fails then there is an underlying issue we cannot solve.2738 const rc = try windows.WSASocketW(
2741 // WSAStartup is supposed to have a pair in the form of WSACleanup to call once all socket operations concluded.2739 @bitCast(i32, domain),
2742 // As of writing that function is never called.2740 @bitCast(i32, filtered_sock_type),
2743 _ = windows.WSAStartup(2, 2) catch {2741 @bitCast(i32, protocol),
2744 return error.NotInitialised;2742 null,
2745 };2743 0,
2746 break :again try windows.WSASocketW(@bitCast(i32, domain), @bitCast(i32, filtered_sock_type), @bitCast(i32, protocol), null, 0, flags);2744 flags,
2747 },2745 );
2748 else => return err,
2749 };
2750 errdefer windows.closesocket(rc) catch unreachable;2746 errdefer windows.closesocket(rc) catch unreachable;
2751 if ((socket_type & SOCK_NONBLOCK) != 0) {2747 if ((socket_type & SOCK_NONBLOCK) != 0) {
2752 var mode: c_ulong = 1; // nonblocking2748 var mode: c_ulong = 1; // nonblocking
lib/std/os/windows.zig+57-11
...@@ -1261,7 +1261,7 @@ pub fn WSAStartup(majorVersion: u8, minorVersion: u8) !ws2_32.WSADATA {...@@ -1261,7 +1261,7 @@ pub fn WSAStartup(majorVersion: u8, minorVersion: u8) !ws2_32.WSADATA {
1261 .WSASYSNOTREADY => return error.SystemNotAvailable,1261 .WSASYSNOTREADY => return error.SystemNotAvailable,
1262 .WSAVERNOTSUPPORTED => return error.VersionNotSupported,1262 .WSAVERNOTSUPPORTED => return error.VersionNotSupported,
1263 .WSAEINPROGRESS => return error.BlockingOperationInProgress,1263 .WSAEINPROGRESS => return error.BlockingOperationInProgress,
1264 .WSAEPROCLIM => return error.SystemResources,1264 .WSAEPROCLIM => return error.ProcessFdQuotaExceeded,
1265 else => |err| return unexpectedWSAError(err),1265 else => |err| return unexpectedWSAError(err),
1266 },1266 },
1267 };1267 };
...@@ -1280,6 +1280,30 @@ pub fn WSACleanup() !void {...@@ -1280,6 +1280,30 @@ pub fn WSACleanup() !void {
1280 };1280 };
1281}1281}
12821282
1283var wsa_startup_mutex: std.Thread.Mutex = .{};
1284
1285/// Microsoft requires WSAStartup to be called to initialize, or else
1286/// WSASocketW will return WSANOTINITIALISED.
1287/// Since this is a standard library, we do not have the luxury of
1288/// putting initialization code anywhere, because we would not want
1289/// to pay the cost of calling WSAStartup if there ended up being no
1290/// networking. Also, if Zig code is used as a library, Zig is not in
1291/// charge of the start code, and we couldn't put in any initialization
1292/// code even if we wanted to.
1293/// The documentation for WSAStartup mentions that there must be a
1294/// matching WSACleanup call. It is not possible for the Zig Standard
1295/// Library to honor this for the same reason - there is nowhere to put
1296/// deinitialization code.
1297/// So, API users of the zig std lib have two options:
1298/// * (recommended) The simple, cross-platform way: just call `WSASocketW`
1299/// and don't worry about it. Zig will call WSAStartup() in a thread-safe
1300/// manner and never deinitialize networking. This is ideal for an
1301/// application which has the capability to do networking.
1302/// * The getting-your-hands-dirty way: call `WSAStartup()` before doing
1303/// networking, so that the error handling code for WSANOTINITIALISED never
1304/// gets run, which then allows the application or library to call `WSACleanup()`.
1305/// This could make sense for a library, which has init and deinit
1306/// functions for the whole library's lifetime.
1283pub fn WSASocketW(1307pub fn WSASocketW(
1284 af: i32,1308 af: i32,
1285 socket_type: i32,1309 socket_type: i32,
...@@ -1288,18 +1312,40 @@ pub fn WSASocketW(...@@ -1288,18 +1312,40 @@ pub fn WSASocketW(
1288 g: ws2_32.GROUP,1312 g: ws2_32.GROUP,
1289 dwFlags: DWORD,1313 dwFlags: DWORD,
1290) !ws2_32.SOCKET {1314) !ws2_32.SOCKET {
1291 const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags);1315 var first = true;
1292 if (rc == ws2_32.INVALID_SOCKET) {1316 while (true) {
1293 switch (ws2_32.WSAGetLastError()) {1317 const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags);
1294 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,1318 if (rc == ws2_32.INVALID_SOCKET) {
1295 .WSAEMFILE => return error.ProcessFdQuotaExceeded,1319 switch (ws2_32.WSAGetLastError()) {
1296 .WSAENOBUFS => return error.SystemResources,1320 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
1297 .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported,1321 .WSAEMFILE => return error.ProcessFdQuotaExceeded,
1298 .WSANOTINITIALISED => return error.NotInitialised,1322 .WSAENOBUFS => return error.SystemResources,
1299 else => |err| return unexpectedWSAError(err),1323 .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported,
1324 .WSANOTINITIALISED => {
1325 if (!first) return error.Unexpected;
1326 first = false;
1327
1328 var held = wsa_startup_mutex.acquire();
1329 defer held.release();
1330
1331 // Here we could use a flag to prevent multiple threads to prevent
1332 // multiple calls to WSAStartup, but it doesn't matter. We're globally
1333 // leaking the resource intentionally, and the mutex already prevents
1334 // data races within the WSAStartup function.
1335 _ = WSAStartup(2, 2) catch |err| switch (err) {
1336 error.SystemNotAvailable => return error.SystemResources,
1337 error.VersionNotSupported => return error.Unexpected,
1338 error.BlockingOperationInProgress => return error.Unexpected,
1339 error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
1340 error.Unexpected => return error.Unexpected,
1341 };
1342 continue;
1343 },
1344 else => |err| return unexpectedWSAError(err),
1345 }
1300 }1346 }
1347 return rc;
1301 }1348 }
1302 return rc;
1303}1349}
13041350
1305pub fn bind(s: ws2_32.SOCKET, name: *const ws2_32.sockaddr, namelen: ws2_32.socklen_t) i32 {1351pub fn bind(s: ws2_32.SOCKET, name: *const ws2_32.sockaddr, namelen: ws2_32.socklen_t) i32 {