mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 20:18:15 +01:00
Host sockets are always non-blocking, so a game's blocking recv(), accept() and so on came back with EAGAIN at once, and connect() and poll() blocked the whole emulator in the host call instead. Now recv, recvfrom, send, sendto, accept, connect, poll and select try once, and if the call would block on a blocking socket (no SO_NBIO or MSG_DONTWAIT), the thread waits and a CoreTiming event retries it every millisecond. It ends when the call completes or fails, or when SO_RCVTIMEO/SO_SNDTIMEO runs out in emulated time. Sockets accepted on Linux are made non-blocking too: unlike BSD and Windows, Linux doesn't pass that on from the listening socket. select() rejects a tv_usec of a second or more with EINVAL. Hardware behaviour from pspautotests net/inet/blocking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
3.8 KiB
C++
129 lines
3.8 KiB
C++
#include "Common/Net/SocketCompat.h"
|
|
#include "Core/HLE/NetInetConstants.h"
|
|
#include "Core/HLE/SocketManager.h"
|
|
#include "Common/Log.h"
|
|
|
|
#include <mutex>
|
|
|
|
SocketManager g_socketManager;
|
|
static std::mutex g_socketMutex; // TODO: Remove once the adhoc thread is gone
|
|
|
|
InetSocket *SocketManager::CreateSocket(int *index, int *returned_errno, SocketState state, int domain, int type, int protocol) {
|
|
_dbg_assert_(state != SocketState::Unused);
|
|
|
|
int hostDomain = convertSocketDomainPSP2Host(domain);
|
|
int hostType = convertSocketTypePSP2Host(type);
|
|
int hostProtocol = convertSocketProtoPSP2Host(protocol);
|
|
|
|
SOCKET hostSock = ::socket(hostDomain, hostType, hostProtocol);
|
|
if (hostSock < 0) {
|
|
*returned_errno = socket_errno;
|
|
return nullptr;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(g_socketMutex);
|
|
|
|
for (int i = MIN_VALID_INET_SOCKET; i < ARRAY_SIZE(inetSockets_); i++) {
|
|
if (inetSockets_[i].state == SocketState::Unused) {
|
|
*index = i;
|
|
InetSocket *inetSock = inetSockets_ + i;
|
|
*inetSock = {}; // Reset to default.
|
|
inetSock->sock = hostSock;
|
|
inetSock->state = state;
|
|
inetSock->domain = domain;
|
|
inetSock->type = type;
|
|
inetSock->protocol = protocol;
|
|
inetSock->nonblocking = false;
|
|
*returned_errno = 0;
|
|
return inetSock;
|
|
}
|
|
}
|
|
_dbg_assert_(false);
|
|
|
|
ERROR_LOG(Log::sceNet, "Ran out of socket handles! This is BAD.");
|
|
closesocket(hostSock);
|
|
*index = 0;
|
|
*returned_errno = ENOMEM; // or something..
|
|
return nullptr;
|
|
}
|
|
|
|
InetSocket *SocketManager::AdoptSocket(int *index, SOCKET hostSocket, const InetSocket *derive) {
|
|
std::lock_guard<std::mutex> guard(g_socketMutex);
|
|
|
|
for (int i = MIN_VALID_INET_SOCKET; i < ARRAY_SIZE(inetSockets_); i++) {
|
|
if (inetSockets_[i].state == SocketState::Unused) {
|
|
*index = i;
|
|
|
|
InetSocket *inetSock = inetSockets_ + i;
|
|
inetSock->sock = hostSocket;
|
|
inetSock->state = derive->state;
|
|
inetSock->domain = derive->domain;
|
|
inetSock->type = derive->type;
|
|
inetSock->protocol = derive->protocol;
|
|
inetSock->nonblocking = derive->nonblocking; // should we inherit blocking state?
|
|
inetSock->recvTimeoutUs = derive->recvTimeoutUs;
|
|
inetSock->sendTimeoutUs = derive->sendTimeoutUs;
|
|
return inetSock;
|
|
}
|
|
}
|
|
|
|
// No space? Return nullptr and let the caller handle it. Shouldn't ever happen.
|
|
*index = 0;
|
|
return nullptr;
|
|
}
|
|
|
|
bool SocketManager::Close(InetSocket *inetSocket) {
|
|
_dbg_assert_(inetSocket->state != SocketState::Unused);
|
|
if (closesocket(inetSocket->sock) != 0) {
|
|
ERROR_LOG(Log::sceNet, "closesocket(%d) failed", inetSocket->sock);
|
|
return false;
|
|
}
|
|
inetSocket->state = SocketState::Unused;
|
|
inetSocket->sock = 0;
|
|
return true;
|
|
}
|
|
|
|
bool SocketManager::GetInetSocket(int sock, InetSocket **inetSocket) {
|
|
std::lock_guard<std::mutex> guard(g_socketMutex);
|
|
if (sock < MIN_VALID_INET_SOCKET || sock >= ARRAY_SIZE(inetSockets_) || inetSockets_[sock].state == SocketState::Unused) {
|
|
*inetSocket = nullptr;
|
|
return false;
|
|
}
|
|
*inetSocket = inetSockets_ + sock;
|
|
return true;
|
|
}
|
|
|
|
// Simplified mappers, only really useful in select/poll
|
|
SOCKET SocketManager::GetHostSocketFromInetSocket(int sock) {
|
|
std::lock_guard<std::mutex> guard(g_socketMutex);
|
|
if (sock < MIN_VALID_INET_SOCKET || sock >= ARRAY_SIZE(inetSockets_) || inetSockets_[sock].state == SocketState::Unused) {
|
|
_dbg_assert_(false);
|
|
return -1;
|
|
}
|
|
if (sock == 0) {
|
|
// Map 0 to 0, special case.
|
|
return 0;
|
|
}
|
|
return inetSockets_[sock].sock;
|
|
}
|
|
|
|
void SocketManager::CloseAll() {
|
|
for (auto &sock : inetSockets_) {
|
|
if (sock.state != SocketState::Unused) {
|
|
closesocket(sock.sock);
|
|
}
|
|
sock.state = SocketState::Unused;
|
|
sock.sock = 0;
|
|
}
|
|
}
|
|
|
|
const char *SocketStateToString(SocketState state) {
|
|
switch (state) {
|
|
case SocketState::Unused: return "unused";
|
|
case SocketState::UsedNetInet: return "netInet";
|
|
case SocketState::UsedProAdhoc: return "proAdhoc";
|
|
default:
|
|
return "N/A";
|
|
}
|
|
}
|