UI: Forget the views' pending requests when recreating them

WipeRequesterToken() only reset the screen's token, after CreateViews() had
already handed it to the new views, so nothing was ever forgotten - not even
in ~Screen(), which saw a token of -1. A text input dialog open across a
RecreateViews() (on the RetroAchievements screen, a slow login failing) would
then write through a deleted PopupTextInputChoice, possibly into the field
that took its place: #21708.

Requests made while creating views now get their own token, forgotten
before the views are deleted. Screen-level requests keep the screen's token.

RequestManager: ForgetRequestsWithToken() now locks, and also drops
responses that arrived but weren't processed yet. ProcessRequests() calls
the callbacks one at a time without holding the lock, so a callback that
closes a screen drops the rest of that screen's responses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-11 21:29:47 +02:00
1 parent c55fff6b7d
commit e2cda3d8f0
6 files changed
+119 -44

No files matched your search

+13 -4
View File
@@ -27,18 +27,27 @@ void Screen::focusChanged(ScreenFocusChange focusChange) {
}
int Screen::GetRequesterToken() {
if (token_ < 0) {
token_ = g_requestManager.GenerateRequesterToken();
int &token = creatingViews_ ? viewsToken_ : token_;
if (token < 0) {
token = g_requestManager.GenerateRequesterToken();
}
return token;
}
void Screen::ForgetViewRequests() {
if (viewsToken_ >= 0) {
g_requestManager.ForgetRequestsWithToken(viewsToken_);
viewsToken_ = -1;
}
return token_;
}
Screen::~Screen() {
screenManager_ = nullptr;
// To avoid expired callbacks getting called.
if (token_ >= 0) {
// To avoid expired callbacks getting called.
g_requestManager.ForgetRequestsWithToken(token_);
}
ForgetViewRequests();
}
const char *DialogResultToString(DialogResult result) {