Apply TCP debug policy on every game session

Start a newly selected production listener on offline rematch, reuse an existing listener, and cleanly release its transport state when disabled. Balance Winsock ownership and failed starts. Native production runtime and four focused tests pass, including six isolated real listener/bulk-write launch cases. Tracking: beads-eio.3.385.
This commit is contained in:
Matthew Stanley
2026-10-24 08:33:51 -07:00
parent 04ce29dc5d
commit dec6ea0924
7 changed files with 100 additions and 20 deletions
+4
View File
@@ -23,6 +23,10 @@ psxrecomp_add_runtime_target(psx-runtime
# (docs/ENHANCEMENTS.md G1.10). Titles get their <exe>_pgxp the same way.
if(BUILD_TESTING)
add_executable(debug_server_policy_test tests/test_debug_server_policy.c src/debug_server_policy.c)
target_include_directories(debug_server_policy_test PRIVATE include)
target_compile_definitions(debug_server_policy_test PRIVATE PSX_NO_DEBUG_TOOLS=1)
add_test(NAME debug_server_policy_test COMMAND debug_server_policy_test)
add_executable(debug_bulk_write_test tests/test_debug_bulk_write.c src/debug_bulk_write.c)
target_include_directories(debug_bulk_write_test PRIVATE include)
add_test(NAME debug_bulk_write_test COMMAND debug_bulk_write_test)
+3
View File
@@ -89,6 +89,9 @@ void debug_server_init(int port);
* Reset the mod request before activating each new session's plugins. */
void debug_server_request(int enabled);
int debug_server_requested(void);
/* Reconcile listener policy after every session's mod activation. Keeps an
* enabled listener, starts a newly enabled one, and closes a disabled one. */
void debug_server_apply_request(int port);
/* Current listener status for heartbeat diagnostics. error is the platform
* socket error captured by init, or 0 when the listener is active. */
+1
View File
@@ -475,6 +475,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_speed.c
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_debug.c
${PSXRECOMP_ROOT}/runtime/src/debug_bulk_write.c
${PSXRECOMP_ROOT}/runtime/src/debug_server_policy.c
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_pgxp.c
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_bezel.c
${PSXRECOMP_ROOT}/runtime/src/mod_builtin_ram.c
+20 -19
View File
@@ -127,24 +127,9 @@ static sock_t s_listen = SOCK_INVALID;
static sock_t s_client = SOCK_INVALID;
static int s_port = DEFAULT_DEBUG_PORT;
static int s_listen_err = 0; /* platform socket error captured by init */
static int s_requested = 0;
void debug_server_request(int enabled)
{
s_requested = enabled != 0;
}
int debug_server_requested(void)
{
const char *env = getenv("PSX_DEBUG_SERVER");
if (env && strcmp(env, "0") == 0) return 0;
if (env && strcmp(env, "1") == 0) return 1;
#ifdef PSX_NO_DEBUG_TOOLS
return s_requested;
#else
return 1;
#ifdef _WIN32
static int s_wsa_started;
#endif
}
#define RECV_BUF_SIZE 8192
static PSX_BSS char s_recv_buf[RECV_BUF_SIZE];
@@ -16121,6 +16106,7 @@ malformed:
void debug_server_init(int port)
{
if (s_listen != SOCK_INVALID) return;
if (port > 0) s_port = port;
/* Deterministic cold-boot controller probe. Unlike a TCP set_input command,
@@ -16218,13 +16204,16 @@ void debug_server_init(int port)
#ifdef _WIN32
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
s_listen_err = WSAStartup(MAKEWORD(2, 2), &wsa);
if (s_listen_err) return;
s_wsa_started = 1;
#endif
s_listen = socket(AF_INET, SOCK_STREAM, 0);
if (s_listen == SOCK_INVALID) {
s_listen_err = sock_error();
fprintf(stdout, "psxrecomp: debug server socket() FAILED\n");
debug_server_shutdown();
return;
}
#ifdef _WIN32
@@ -16248,6 +16237,7 @@ void debug_server_init(int port)
fprintf(stdout, "psxrecomp: debug server bind(%d) FAILED\n", s_port);
sock_close(s_listen);
s_listen = SOCK_INVALID;
debug_server_shutdown();
return;
}
@@ -16276,6 +16266,10 @@ void debug_server_init(int port)
s_io_thread = SDL_CreateThread(io_thread_main, "psx-dbg-io", NULL);
if (!s_io_thread) s_io_running = 0;
}
if (!s_io_thread) {
debug_server_shutdown();
return;
}
if (!s_frame_history) {
s_frame_history = (PSXFrameRecord *)calloc(FRAME_HISTORY_CAP, sizeof(PSXFrameRecord));
@@ -16859,8 +16853,15 @@ void debug_server_shutdown(void)
sock_close(s_client);
s_client = SOCK_INVALID;
}
if (s_io_mutex) { SDL_DestroyMutex(s_io_mutex); s_io_mutex = NULL; }
if (s_io_req_cv) { SDL_DestroyCond(s_io_req_cv); s_io_req_cv = NULL; }
if (s_io_resp_cv) { SDL_DestroyCond(s_io_resp_cv); s_io_resp_cv = NULL; }
free(s_resp_buf);
s_resp_buf = NULL;
s_resp_cap = s_resp_len = 0;
s_io_state = IO_IDLE;
#ifdef _WIN32
WSACleanup();
if (s_wsa_started) { WSACleanup(); s_wsa_started = 0; }
#endif
}
+30
View File
@@ -0,0 +1,30 @@
#include "debug_server.h"
#include <stdlib.h>
#include <string.h>
static int requested;
void debug_server_request(int enabled) { requested = enabled != 0; }
int debug_server_requested(void)
{
const char *env = getenv("PSX_DEBUG_SERVER");
if (env && strcmp(env, "0") == 0) return 0;
if (env && strcmp(env, "1") == 0) return 1;
#ifdef PSX_NO_DEBUG_TOOLS
return requested;
#else
return 1;
#endif
}
void debug_server_apply_request(int port)
{
int listening = 0;
debug_server_get_status(&listening, NULL, NULL);
if (debug_server_requested()) {
if (!listening) debug_server_init(port);
} else if (listening) {
debug_server_shutdown();
}
}
+1 -1
View File
@@ -18844,13 +18844,13 @@ session_reboot:
};
memcard_init_slots(memcard_dir_str.c_str(), slots);
}
debug_server_apply_request(debug_port);
if (!rematch_session) {
std::atexit(memcard_flush_all);
/* Persist the game's native OPTION settings on any exit path (belt-and-
* suspenders; save-on-change already persists them live). No-op if not yet
* armed, so it can't overwrite the saved file with boot defaults. */
std::atexit(game_options_save_now);
if (debug_server_requested()) debug_server_init(debug_port);
#ifndef PSX_NO_DEBUG_TOOLS
host_sampler_start(); /* this is the emulation thread */
#endif
+41
View File
@@ -0,0 +1,41 @@
#include "debug_server.h"
#include <stdio.h>
#include <stdlib.h>
static int listening, starts, stops, last_port, failures;
void debug_server_get_status(int *active, int *port, int *error) {
if (active) *active=listening;
if (port) *port=last_port;
if (error) *error=0;
}
void debug_server_init(int port) { ++starts;listening=1;last_port=port; }
void debug_server_shutdown(void) { ++stops;listening=0; }
#define CHECK(x) do { if (!(x)) { fprintf(stderr,"line %d: %s\n",__LINE__,#x);++failures; } } while(0)
static void env_choice(const char *value) {
#ifdef _WIN32
_putenv_s("PSX_DEBUG_SERVER",value?value:"");
#else
if(value)setenv("PSX_DEBUG_SERVER",value,1);else unsetenv("PSX_DEBUG_SERVER");
#endif
}
int main(void) {
env_choice(NULL);
debug_server_request(0);debug_server_apply_request(4370);
CHECK(!listening && starts==0 && stops==0);
/* First session was vanilla netplay; the next offline session selects it. */
debug_server_request(1);debug_server_apply_request(4370);
CHECK(listening && starts==1 && last_port==4370);
debug_server_apply_request(4370);
CHECK(listening && starts==1 && stops==0);
debug_server_request(0);debug_server_apply_request(4370);
CHECK(!listening && stops==1);
debug_server_apply_request(4370);CHECK(stops==1);
debug_server_request(1);env_choice("0");debug_server_apply_request(4370);
CHECK(!listening && starts==1);
debug_server_request(0);env_choice("1");debug_server_apply_request(4488);
CHECK(listening && starts==2 && last_port==4488);
env_choice(NULL);debug_server_apply_request(4488);
CHECK(!listening && stops==2);
if(!failures)puts("PASS debug server session policy: late enable, reuse, disable, overrides");
return failures?1:0;
}