New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: race-conditions in GUI updates when downloading HTTP files #11639
Conversation
a34acc4
to
73037f2
Compare
73037f2
to
56c6df4
Compare
(cherry picked from commit 56c6df4702015fda7cc7a05b67bfe90b3ede1ad0) See: OpenTTD/OpenTTD#11636 See: OpenTTD/OpenTTD#11639
56c6df4
to
f8c6cd8
Compare
src/network/network_content.cpp
Outdated
if (data != nullptr) { | ||
free(data); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
free
accepts nullptr
to reduce these kinds of special cases needing to be written.
if (data != nullptr) { | |
free(data); | |
} | |
free(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old habit. We once had a platform where that caused a crash. It didn't follow POSIX all that well :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using std::unique_ptr<char[]>
instead of const char *
in the signature of OnReceiveData
and in class HTTPThreadSafeCallback
may remove the need to have these manual calls to free at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, did consider that, but the WinHTTP backend needs a char *
pointer to fill; so that gets a bit tricky, especially as WinHTTP wants it in a bit annoying way. So the only way I saw to do a unique_ptr
was to allocate yet another buffer, and copy it over. Which felt a bit meh at that point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to construct the new []char
and the unique_ptr
which becomes responsible for it at the same time.
You could do: char *buffer = size == 0 ? nullptr : new char[size];
in WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE.
Then construct your std::unique_ptr<char[]>(static_cast<char *>(info))
in WINHTTP_CALLBACK_STATUS_READ_COMPLETE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I did this correct; at least, it works as expected :P
5e9c4ac
to
ef254a7
Compare
ef254a7
to
67c6a95
Compare
Motivation / Problem
Fixes #11636.
HTTP downloads, both on WIndows and Linux, are processed in a thread. This thread calls
HTTPCallback
object with updates, but these are executed from that download-thread. However, that code assumes it is called from the OpenTTD Game thread, and updates things on the GUI. This turns out to be a bit of a mistake, and causes all kinds of (small) race-conditions.Description
Put a bit of glue between the
HTTPCallback
and the HTTP downloaders, calledHTTPThreadSafeCallback
. This Thread-Safe callback puts the ReceiveData and Failure information on a queue, which is dequeue from the OpenTTD Game thread. That way, thread-safety is restored again, and in result, the Thread Analyzer no longer finds any mistakes.Sadly, due to how the current code is designed, this gives the need for a bit of mutex magic, to ensure things can be added and removed in moments that are safe to do so. The main issue here is that a callback can cause a new HTTP request to be created, which makes it a tiny bit more messy than I would like.
One fundamental change required here, is that
OnReceiveData
becomes the owner ofdata
. As otherwise the HTTP downloader would already release the buffer before it could have been processed. On Windows, we already allocated a buffer ourselves. On Linux, this requires allocating another (temporary) buffer, and copying the content. A tiny bit of a slow-down, but as HTTP happens rarely, not something anyone should notice.Limitations
There is another problem here, that I didn't dare to address.
NetworkContent
'sOnReceiveData
is .. special. It really became hard to read and hard to understand. In an ideal world, we would rewrite that piece of code, making thatOnReceiveData
thread-safe. That would be much more efficient. But, that also increases the chances of introduces new bugs. So I went for this approach to avoid that rabbit hole. TheNetworkContent
'sOnReceiveData
is not broken; just .. hard to make thread-safe in its current state.(for those who don't want to open up the code: it is built under the assumption the first request is always to the BaNaNaS mirror redirect service, which returns the URLs of all the files to download; the 2nd and next requests are then one by one an URL from that list. Instead that this is two HTTP handlers, one simply writing a file, the other handling the list, this is build in a single function with some nasty complexity).
Checklist for review
Some things are not automated, and forgotten often. This list is a reminder for the reviewers.