From dec6ea0924d3a0a08ea28a0bbef908a6bb498d80 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 24 Oct 2026 08:33:51 -0700 Subject: [PATCH] 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. --- runtime/CMakeLists.txt | 4 +++ runtime/include/debug_server.h | 3 ++ runtime/runtime.cmake | 1 + runtime/src/debug_server.c | 39 +++++++++++----------- runtime/src/debug_server_policy.c | 30 +++++++++++++++++ runtime/src/main.cpp | 2 +- runtime/tests/test_debug_server_policy.c | 41 ++++++++++++++++++++++++ 7 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 runtime/src/debug_server_policy.c create mode 100644 runtime/tests/test_debug_server_policy.c diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index a7131a2b..d6385167 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -23,6 +23,10 @@ psxrecomp_add_runtime_target(psx-runtime # (docs/ENHANCEMENTS.md G1.10). Titles get their _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) diff --git a/runtime/include/debug_server.h b/runtime/include/debug_server.h index 63bdc66d..99303719 100644 --- a/runtime/include/debug_server.h +++ b/runtime/include/debug_server.h @@ -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. */ diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index b1cd4e5c..d5e4f309 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -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 diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index fb5e1063..3a9f8ebc 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -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 } diff --git a/runtime/src/debug_server_policy.c b/runtime/src/debug_server_policy.c new file mode 100644 index 00000000..abf1000b --- /dev/null +++ b/runtime/src/debug_server_policy.c @@ -0,0 +1,30 @@ +#include "debug_server.h" +#include +#include + +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(); + } +} diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index ccd4314d..61e18f18 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -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 diff --git a/runtime/tests/test_debug_server_policy.c b/runtime/tests/test_debug_server_policy.c new file mode 100644 index 00000000..d83978d8 --- /dev/null +++ b/runtime/tests/test_debug_server_policy.c @@ -0,0 +1,41 @@ +#include "debug_server.h" +#include +#include + +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; +}