PINE: Tidy up and fix shutdown hang on Linux

This commit is contained in:
Stenzek 2024-01-09 19:01:08 +10:00 committed by Connor McLaughlin
parent 40ead584d7
commit 2257992a3f
3 changed files with 139 additions and 94 deletions

View File

@ -1,51 +1,85 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include <stdio.h>
#include <stdlib.h>
#include "Common.h"
#include "Memory.h"
#include "Elfheader.h"
#include "SysForwardDefs.h"
#include "PINE.h"
#include "VMManager.h"
#include "svnrev.h"
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#if _WIN32
#define read_portable(a, b, c) (recv(a, (char*)b, c, 0))
#define write_portable(a, b, c) (send(a, (const char*)b, c, 0))
#define close_portable(a) (closesocket(a))
#define safe_close_portable(a) \
do \
{ \
if ((a) >= 0) \
{ \
closesocket((a)); \
(a) = INVALID_SOCKET; \
} \
} while (0)
#define bzero(b, len) (memset((b), '\0', (len)), (void)0)
#include "common/RedtapeWindows.h"
#include <WinSock2.h>
#include <windows.h>
#else
#define read_portable(a, b, c) (read(a, b, c))
#define write_portable(a, b, c) (write(a, b, c))
#define close_portable(a) (close(a))
#define safe_close_portable(a) \
do \
{ \
if ((a) >= 0) \
{ \
close((a)); \
(a) = -1; \
} \
} while (0)
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#endif
#include "Common.h"
#include "Memory.h"
#include "Elfheader.h"
#include "svnrev.h"
#include "SysForwardDefs.h"
#include "PINE.h"
#include "VMManager.h"
#include "fmt/format.h"
PINEServer::PINEServer() {}
#ifdef _WIN32
static bool InitializeWinsock()
{
static bool initialized = false;
if (initialized)
return true;
WSADATA wsa = {};
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return false;
initialized = true;
std::atexit([]() { WSACleanup(); });
return true;
}
#endif
PINEServer::PINEServer() = default;
PINEServer::~PINEServer()
{
Deinitialize();
// Should be shut down by VMManager.
pxAssert(!IsInitialized());
}
bool PINEServer::Initialize(int slot)
{
m_end.store(false, std::memory_order_release);
m_slot = slot;
#ifdef _WIN32
WSADATA wsa;
struct sockaddr_in server;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
#ifdef _WIN32
if (!InitializeWinsock())
{
Console.WriteLn(Color_Red, "PINE: Cannot initialize winsock! Shutting down...");
Deinitialize();
@ -60,10 +94,9 @@ bool PINEServer::Initialize(int slot)
return false;
}
// yes very good windows s/sun/sin/g sure is fine
sockaddr_in server = {};
server.sin_family = AF_INET;
// localhost only
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost only
server.sin_port = htons(slot);
if (bind(m_sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
@ -151,37 +184,47 @@ std::vector<u8>& PINEServer::MakeFailIPC(std::vector<u8>& ret_buffer, uint32_t s
return ret_buffer;
}
int PINEServer::StartSocket()
bool PINEServer::AcceptClient()
{
m_msgsock = accept(m_sock, 0, 0);
if (m_msgsock == -1)
if (m_msgsock >= 0)
{
// everything else is non recoverable in our scope
// we also mark as recoverable socket errors where it would block a
// non blocking socket, even though our socket is blocking, in case
// we ever have to implement a non blocking socket.
#ifdef _WIN32
int errno_w = WSAGetLastError();
if (!(errno_w == WSAECONNRESET || errno_w == WSAEINTR || errno_w == WSAEINPROGRESS || errno_w == WSAEMFILE || errno_w == WSAEWOULDBLOCK))
{
#else
if (!(errno == ECONNABORTED || errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
{
#endif
fprintf(stderr, "PINE: An unrecoverable error happened! Shutting down...\n");
m_end = true;
return -1;
}
Console.WriteLn("PINE: New client with FD %d connected.", static_cast<int>(m_msgsock));
return true;
}
return 0;
// everything else is non recoverable in our scope
// we also mark as recoverable socket errors where it would block a
// non blocking socket, even though our socket is blocking, in case
// we ever have to implement a non blocking socket.
#ifdef _WIN32
const int errno_w = WSAGetLastError();
if (!(errno_w == WSAECONNRESET || errno_w == WSAEINTR || errno_w == WSAEINPROGRESS || errno_w == WSAEMFILE || errno_w == WSAEWOULDBLOCK) && m_sock != INVALID_SOCKET)
Console.Error("PINE: accept() returned error %d", errno_w);
#else
if (!(errno == ECONNABORTED || errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) && m_sock >= 0)
Console.Error("PINE: accept() returned error %d", errno);
#endif
return false;
}
void PINEServer::MainLoop()
{
if (StartSocket() < 0)
return;
while (!m_end.load(std::memory_order_acquire))
{
if (!AcceptClient())
continue;
ClientLoop();
Console.WriteLn("PINE: Client disconnected.");
safe_close_portable(m_msgsock);
}
}
void PINEServer::ClientLoop()
{
while (!m_end.load(std::memory_order_acquire))
{
// either int or ssize_t depending on the platform, so we have to
@ -198,12 +241,7 @@ void PINEServer::MainLoop()
// we recreate the socket if an error happens
if (tmp_length <= 0)
{
receive_length = 0;
if (StartSocket() < 0)
return;
break;
}
return;
receive_length += tmp_length;
@ -232,31 +270,37 @@ void PINEServer::MainLoop()
// if we cannot send back our answer restart the socket
if (write_portable(m_msgsock, res.buffer.data(), res.size) < 0)
{
if (StartSocket() < 0)
return;
}
return;
}
}
return;
}
void PINEServer::Deinitialize()
{
m_end.store(true, std::memory_order_release);
#ifdef _WIN32
WSACleanup();
#else
unlink(m_socket_name.c_str());
#ifndef _WIN32
if (!m_socket_name.empty())
{
unlink(m_socket_name.c_str());
m_socket_name = {};
}
#endif
close_portable(m_sock);
close_portable(m_msgsock);
// shutdown() is needed, otherwise accept() will still block.
#ifdef _WIN32
if (m_sock != INVALID_SOCKET)
shutdown(m_sock, SD_BOTH);
#else
if (m_sock >= 0)
shutdown(m_sock, SHUT_RDWR);
#endif
safe_close_portable(m_sock);
safe_close_portable(m_msgsock);
if (m_thread.joinable())
{
m_thread.join();
}
}
PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8>& ret_buffer, u32 buf_size)
@ -485,16 +529,17 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
goto error;
EmuStatus status;
switch (VMManager::GetState()) {
case VMState::Running:
status = EmuStatus::Running;
break;
case VMState::Paused:
status = EmuStatus::Paused;
break;
default:
status = EmuStatus::Shutdown;
break;
switch (VMManager::GetState())
{
case VMState::Running:
status = EmuStatus::Running;
break;
case VMState::Paused:
status = EmuStatus::Paused;
break;
default:
status = EmuStatus::Shutdown;
break;
}
ToResultVector(ret_buffer, status, ret_cnt);

View File

@ -38,11 +38,13 @@ protected:
#else
// absolute path of the socket. Stored in XDG_RUNTIME_DIR, if unset /tmp
std::string m_socket_name;
int m_sock = 0;
int m_sock = -1;
// the message socket used in thread's accept().
int m_msgsock = 0;
int m_msgsock = -1;
#endif
// Whether the socket processing thread should stop executing/is stopped.
std::atomic_bool m_end{true};
/**
* Maximum memory used by an IPC message request.
@ -131,6 +133,7 @@ protected:
// Thread used to relay IPC commands.
void MainLoop();
void ClientLoop();
/**
* Internal function, Parses an IPC command.
@ -153,9 +156,8 @@ protected:
/**
* Initializes an open socket for IPC communication.
* return value: -1 if a fatal failure happened, 0 otherwise.
*/
int StartSocket();
bool AcceptClient();
/**
* Converts a primitive value to bytes in little endian
@ -190,18 +192,18 @@ protected:
static inline bool SafetyChecks(u32 command_len, int command_size, u32 reply_len, int reply_size = 0, u32 buf_size = MAX_IPC_SIZE - 1)
{
return !((command_len + command_size) > buf_size ||
(reply_len + reply_size) >= MAX_IPC_RETURN_SIZE);
(reply_len + reply_size) >= MAX_IPC_RETURN_SIZE);
}
public:
// Whether the socket processing thread should stop executing/is stopped.
std::atomic_bool m_end{ false };
int m_slot;
/* Initializers */
PINEServer();
virtual ~PINEServer();
~PINEServer();
__fi bool IsInitialized() const { return !m_end.load(std::memory_order_acquire); }
bool Initialize(int slot = PINE_DEFAULT_SLOT);
void Deinitialize();
}; // class SocketIPC

View File

@ -3250,18 +3250,16 @@ const std::vector<u32>& VMManager::GetSortedProcessorList()
void VMManager::ReloadPINE()
{
if (EmuConfig.EnablePINE && (s_pine_server.m_slot != EmuConfig.PINESlot || s_pine_server.m_end))
{
if (!s_pine_server.m_end)
{
s_pine_server.Deinitialize();
}
s_pine_server.Initialize(EmuConfig.PINESlot);
}
else if ((!EmuConfig.EnablePINE && !s_pine_server.m_end))
{
const bool needs_reinit = (EmuConfig.EnablePINE != s_pine_server.IsInitialized() ||
s_pine_server.m_slot != EmuConfig.PINESlot);
if (!needs_reinit)
return;
if (s_pine_server.IsInitialized())
s_pine_server.Deinitialize();
}
if (EmuConfig.EnablePINE)
s_pine_server.Initialize();
}
void VMManager::InitializeDiscordPresence()