Skip to content

Fix/achievements winhttp timeout crash#14311

Open
salas-dev-max wants to merge 1 commit intoPCSX2:masterfrom
salas-dev-max:fix/achievements-winhttp-timeout-crash
Open

Fix/achievements winhttp timeout crash#14311
salas-dev-max wants to merge 1 commit intoPCSX2:masterfrom
salas-dev-max:fix/achievements-winhttp-timeout-crash

Conversation

@salas-dev-max
Copy link
Copy Markdown

Description of Changes

Fix crash when RetroAchievements server is unreachable due to a censored or blocking network connection. Affects any network that hangs connections rather than refusing them (error 12002 / ERROR_WINHTTP_TIMEOUT).

Rationale behind Changes

Three bugs in the WinHTTP layer caused a crash instead of a graceful failure:

  1. State overwrite in StartRequest() — when WinHttpSendRequest fails synchronously, the request state was correctly set to Complete but then immediately overwritten with Started unconditionally. The async callback never fires for a failed send, so the request hung until the app-level timeout.

  2. Competing timeouts — WinHTTP's default receive timeout and the app-level timeout are both 30s, creating a race between the WinHTTP callback thread and the polling thread. Explicit WinHTTP timeouts (15s) ensure the async error always fires before the poll loop races it.

  3. Incorrect error mapping in ClientServerCall()HTTP_STATUS_ERROR was mapped to RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR, causing rc_client to retry indefinitely on hard WinHTTP failures. On a network that consistently hangs connections this retry loop leads to a crash. Fixed by mapping it to CLIENT_ERROR (non-retryable).

Closes #14277

Suggested Testing Steps

Add 127.0.0.1 retroachievements.org to your hosts file, launch PCSX2 with achievements enabled, and verify it logs an error and continues running instead of crashing after ~30 seconds.

Did you use AI to help find, test, or implement this issue or feature?

Yes. Used Claude to help trace the root cause through the WinHTTP callback chain and identify the three contributing bugs. All changes were reviewed and understood before submission.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for submitting a contribution to PCSX2

As this is your first pull request, please be aware of the contributing guidelines.

Additionally, as per recent changes in GitHub Actions, your pull request will need to be approved by a maintainer before GitHub Actions can run against it. You can find more information about this change here.

Please be patient until this happens. In the meantime if you'd like to confirm the builds are passing, you have the option of opening a PR on your own fork, just make sure your fork's master branch is up to date!

@Mrlinkwii
Copy link
Copy Markdown
Contributor

@salas-dev-max some windows CI are failing :)

@salas-dev-max
Copy link
Copy Markdown
Author

@salas-dev-max some windows CI are failing :)

Darn

Copy link
Copy Markdown
Member

@F0bes F0bes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial feedback, not tested.

Squash your commits. Commits should start capitalized.

Console.Error("WinHttpSendRequest() failed: %u", GetLastError());
req->status_code = HTTP_STATUS_ERROR;
req->state.store(Request::State::Complete);
Console.Error("WinHttpSendRequest() failed: %u", GetLastError());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please indent only using tabs.

DevCon.WriteLn("Started HTTP request for '%s'", req->url.c_str());
req->state = Request::State::Started;
req->start_time = Common::Timer::GetCurrentValue();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we've failed, should we not return false?

Comment thread pcsx2/Achievements.cpp
RC_API_SERVER_RESPONSE_CLIENT_ERROR :
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR)
: status_code;
RC_API_SERVER_RESPONSE_CLIENT_ERROR :
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the indenting here too.

Comment thread pcsx2/Achievements.cpp
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR)
: status_code;
RC_API_SERVER_RESPONSE_CLIENT_ERROR :
status_code == HTTPDownloader::HTTP_STATUS_TIMEOUT ?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather use some temporaries to make it more readable.

Something like this, or even some if statements is more readable imo

const bool is_error = (status_code <= 0);
const bool is_retryable =
	is_error && (status_code == HTTPDownloader::HTTP_STATUS_TIMEOUT);

rr.http_status_code =
	is_error
	? (is_retryable
		? RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR
		: RC_API_SERVER_RESPONSE_CLIENT_ERROR)
	: status_code;

- Fix state overwrite in StartRequest() where Complete was clobbered
  by Started when WinHttpSendRequest failed synchronously
- Set explicit WinHTTP timeouts (15s) shorter than app-level timeout
  to eliminate the polling/callback race on slow connections
- Map HTTP_STATUS_ERROR to CLIENT_ERROR (non-retryable) to prevent
  rc_client retry loops on hard WinHTTP failures (error 12002/timeout)

Closes PCSX2#14277
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: PCSX2 crashed after start with bat WAN connection

3 participants