Improve behavior when sending to closed connection
- On Unix, this would previously kill the Yuzu process with SIGPIPE. Send MSG_NOSIGNAL to opt out of this. - Add support for the proper error code in this situation, EPIPE. - Windows has nonstandard behavior in this situation; translate it to the standard behavior. Kind of pointless, but isn't it nice to be correct?
This commit is contained in:
		| @@ -18,7 +18,9 @@ enum class Errno : u32 { | ||||
|     AGAIN = 11, | ||||
|     INVAL = 22, | ||||
|     MFILE = 24, | ||||
|     PIPE = 32, | ||||
|     MSGSIZE = 90, | ||||
|     CONNABORTED = 103, | ||||
|     CONNRESET = 104, | ||||
|     NOTCONN = 107, | ||||
|     TIMEDOUT = 110, | ||||
|   | ||||
| @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { | ||||
|         return Errno::INVAL; | ||||
|     case Network::Errno::MFILE: | ||||
|         return Errno::MFILE; | ||||
|     case Network::Errno::PIPE: | ||||
|         return Errno::PIPE; | ||||
|     case Network::Errno::NOTCONN: | ||||
|         return Errno::NOTCONN; | ||||
|     case Network::Errno::TIMEDOUT: | ||||
|         return Errno::TIMEDOUT; | ||||
|     case Network::Errno::CONNABORTED: | ||||
|         return Errno::CONNABORTED; | ||||
|     case Network::Errno::CONNRESET: | ||||
|         return Errno::CONNRESET; | ||||
|     case Network::Errno::INPROGRESS: | ||||
|   | ||||
| @@ -39,6 +39,11 @@ namespace Network { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| enum class CallType { | ||||
|     Send, | ||||
|     Other, | ||||
| }; | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|  | ||||
| using socklen_t = int; | ||||
| @@ -96,7 +101,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { | ||||
|     return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; | ||||
| } | ||||
|  | ||||
| Errno TranslateNativeError(int e) { | ||||
| Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { | ||||
|     switch (e) { | ||||
|     case 0: | ||||
|         return Errno::SUCCESS; | ||||
| @@ -112,6 +117,14 @@ Errno TranslateNativeError(int e) { | ||||
|         return Errno::AGAIN; | ||||
|     case WSAECONNREFUSED: | ||||
|         return Errno::CONNREFUSED; | ||||
|     case WSAECONNABORTED: | ||||
|         if (call_type == CallType::Send) { | ||||
|             // Winsock yields WSAECONNABORTED from `send` in situations where Unix | ||||
|             // systems, and actual Switches, yield EPIPE. | ||||
|             return Errno::PIPE; | ||||
|         } else { | ||||
|             return Errno::CONNABORTED; | ||||
|         } | ||||
|     case WSAECONNRESET: | ||||
|         return Errno::CONNRESET; | ||||
|     case WSAEHOSTUNREACH: | ||||
| @@ -198,7 +211,7 @@ bool EnableNonBlock(int fd, bool enable) { | ||||
|     return fcntl(fd, F_SETFL, flags) == 0; | ||||
| } | ||||
|  | ||||
| Errno TranslateNativeError(int e) { | ||||
| Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { | ||||
|     switch (e) { | ||||
|     case 0: | ||||
|         return Errno::SUCCESS; | ||||
| @@ -208,6 +221,10 @@ Errno TranslateNativeError(int e) { | ||||
|         return Errno::INVAL; | ||||
|     case EMFILE: | ||||
|         return Errno::MFILE; | ||||
|     case EPIPE: | ||||
|         return Errno::PIPE; | ||||
|     case ECONNABORTED: | ||||
|         return Errno::CONNABORTED; | ||||
|     case ENOTCONN: | ||||
|         return Errno::NOTCONN; | ||||
|     case EAGAIN: | ||||
| @@ -236,13 +253,13 @@ Errno TranslateNativeError(int e) { | ||||
|  | ||||
| #endif | ||||
|  | ||||
| Errno GetAndLogLastError() { | ||||
| Errno GetAndLogLastError(CallType call_type = CallType::Other) { | ||||
| #ifdef _WIN32 | ||||
|     int e = WSAGetLastError(); | ||||
| #else | ||||
|     int e = errno; | ||||
| #endif | ||||
|     const Errno err = TranslateNativeError(e); | ||||
|     const Errno err = TranslateNativeError(e, call_type); | ||||
|     if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { | ||||
|         // These happen during normal operation, so only log them at debug level. | ||||
|         LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); | ||||
| @@ -731,13 +748,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { | ||||
|     ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); | ||||
|     ASSERT(flags == 0); | ||||
|  | ||||
|     int native_flags = 0; | ||||
| #if YUZU_UNIX | ||||
|     native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE | ||||
| #endif | ||||
|     const auto result = send(fd, reinterpret_cast<const char*>(message.data()), | ||||
|                              static_cast<int>(message.size()), 0); | ||||
|                              static_cast<int>(message.size()), native_flags); | ||||
|     if (result != SOCKET_ERROR) { | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
|     return {-1, GetAndLogLastError(CallType::Send)}; | ||||
| } | ||||
|  | ||||
| std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | ||||
| @@ -759,7 +780,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, | ||||
|         return {static_cast<s32>(result), Errno::SUCCESS}; | ||||
|     } | ||||
|  | ||||
|     return {-1, GetAndLogLastError()}; | ||||
|     return {-1, GetAndLogLastError(CallType::Send)}; | ||||
| } | ||||
|  | ||||
| Errno Socket::Close() { | ||||
|   | ||||
| @@ -33,10 +33,12 @@ enum class Errno { | ||||
|     BADF, | ||||
|     INVAL, | ||||
|     MFILE, | ||||
|     PIPE, | ||||
|     NOTCONN, | ||||
|     AGAIN, | ||||
|     CONNREFUSED, | ||||
|     CONNRESET, | ||||
|     CONNABORTED, | ||||
|     HOSTUNREACH, | ||||
|     NETDOWN, | ||||
|     NETUNREACH, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user