Fix: race-conditions in GUI updates when downloading HTTP files
(cherry picked from commit 56c6df4702015fda7cc7a05b67bfe90b3ede1ad0) See: https://github.com/OpenTTD/OpenTTD/issues/11636 See: https://github.com/OpenTTD/OpenTTD/pull/11639
This commit is contained in:
		 Patric Stout
					Patric Stout
				
			
				
					committed by
					
						 Jonathan G Rennison
						Jonathan G Rennison
					
				
			
			
				
	
			
			
			 Jonathan G Rennison
						Jonathan G Rennison
					
				
			
						parent
						
							673a0dc5de
						
					
				
				
					commit
					6e7c92e3af
				
			| @@ -15,6 +15,7 @@ | ||||
| #include "../network_internal.h" | ||||
|  | ||||
| #include "http.h" | ||||
| #include "http_shared.h" | ||||
|  | ||||
| #include <mutex> | ||||
| #include <winhttp.h> | ||||
| @@ -26,9 +27,9 @@ static HINTERNET _winhttp_session = nullptr; | ||||
| /** Single HTTP request. */ | ||||
| class NetworkHTTPRequest { | ||||
| private: | ||||
| 	const std::wstring uri;       ///< URI to connect to. | ||||
| 	HTTPCallback *callback;       ///< Callback to send data back on. | ||||
| 	const std::string data;       ///< Data to send, if any. | ||||
| 	const std::wstring uri; ///< URI to connect to. | ||||
| 	HTTPThreadSafeCallback callback; ///< Callback to send data back on. | ||||
| 	const std::string data; ///< Data to send, if any. | ||||
|  | ||||
| 	HINTERNET connection = nullptr;      ///< Current connection object. | ||||
| 	HINTERNET request = nullptr;         ///< Current request object. | ||||
| @@ -49,6 +50,11 @@ static std::vector<NetworkHTTPRequest *> _http_requests; | ||||
| static std::vector<NetworkHTTPRequest *> _new_http_requests; | ||||
| static std::mutex _new_http_requests_mutex; | ||||
|  | ||||
| static std::vector<HTTPThreadSafeCallback *> _http_callbacks; | ||||
| static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks; | ||||
| static std::mutex _http_callback_mutex; | ||||
| static std::mutex _new_http_callback_mutex; | ||||
|  | ||||
| /** | ||||
|  * Create a new HTTP request. | ||||
|  * | ||||
| @@ -61,6 +67,8 @@ NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *ca | ||||
| 	callback(callback), | ||||
| 	data(data) | ||||
| { | ||||
| 	std::lock_guard<std::mutex> lock(_new_http_callback_mutex); | ||||
| 	_new_http_callbacks.push_back(&this->callback); | ||||
| } | ||||
|  | ||||
| static std::string GetLastErrorAsString() | ||||
| @@ -113,7 +121,7 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) | ||||
| 			if (this->depth++ > 5) { | ||||
| 				Debug(net, 0, "HTTP request failed: too many redirects"); | ||||
| 				this->finished = true; | ||||
| 				this->callback->OnFailure(); | ||||
| 				this->callback.OnFailure(); | ||||
| 				return; | ||||
| 			} | ||||
| 			break; | ||||
| @@ -136,7 +144,7 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) | ||||
| 				/* No need to be verbose about rate limiting. */ | ||||
| 				Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0, "HTTP request failed: status-code {}", status_code); | ||||
| 				this->finished = true; | ||||
| 				this->callback->OnFailure(); | ||||
| 				this->callback.OnFailure(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| @@ -150,7 +158,7 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) | ||||
| 			DWORD size = *(DWORD *)info; | ||||
|  | ||||
| 			/* Next step: read the data in a temporary allocated buffer. | ||||
| 			 * The buffer will be free'd in the next step. */ | ||||
| 			 * The buffer will be free'd by OnReceiveData() in the next step. */ | ||||
| 			char *buffer = size == 0 ? nullptr : MallocT<char>(size); | ||||
| 			WinHttpReadData(this->request, buffer, size, 0); | ||||
| 		} break; | ||||
| @@ -158,9 +166,7 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) | ||||
| 		case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: | ||||
| 			Debug(net, 4, "HTTP callback: {} bytes", length); | ||||
|  | ||||
| 			this->callback->OnReceiveData(static_cast<char *>(info), length); | ||||
| 			/* Free the temporary buffer that was allocated in the previous step. */ | ||||
| 			free(info); | ||||
| 			this->callback.OnReceiveData(static_cast<char *>(info), length); | ||||
|  | ||||
| 			if (length == 0) { | ||||
| 				/* Next step: no more data available: request is finished. */ | ||||
| @@ -177,13 +183,13 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length) | ||||
| 		case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: | ||||
| 			Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); | ||||
| 			this->finished = true; | ||||
| 			this->callback->OnFailure(); | ||||
| 			this->callback.OnFailure(); | ||||
| 			break; | ||||
|  | ||||
| 		default: | ||||
| 			Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code); | ||||
| 			this->finished = true; | ||||
| 			this->callback->OnFailure(); | ||||
| 			this->callback.OnFailure(); | ||||
| 			return; | ||||
| 	} | ||||
| } | ||||
| @@ -227,7 +233,7 @@ void NetworkHTTPRequest::Connect() | ||||
| 	if (this->connection == nullptr) { | ||||
| 		Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); | ||||
| 		this->finished = true; | ||||
| 		this->callback->OnFailure(); | ||||
| 		this->callback.OnFailure(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| @@ -237,7 +243,7 @@ void NetworkHTTPRequest::Connect() | ||||
|  | ||||
| 		Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString()); | ||||
| 		this->finished = true; | ||||
| 		this->callback->OnFailure(); | ||||
| 		this->callback.OnFailure(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| @@ -258,14 +264,17 @@ void NetworkHTTPRequest::Connect() | ||||
|  */ | ||||
| bool NetworkHTTPRequest::Receive() | ||||
| { | ||||
| 	if (this->callback->IsCancelled()) { | ||||
| 	if (this->callback.cancelled && !this->finished) { | ||||
| 		Debug(net, 1, "HTTP request failed: cancelled by user"); | ||||
| 		this->finished = true; | ||||
| 		this->callback->OnFailure(); | ||||
| 		return true; | ||||
| 		this->callback.OnFailure(); | ||||
| 		/* Fall-through, as we are waiting for IsQueueEmpty() to happen. */ | ||||
| 	} | ||||
|  | ||||
| 	return this->finished; | ||||
| 	/* Only return true if the queue was also dequeued. */ | ||||
| 	if (!this->finished) return false; | ||||
| 	if (!this->callback.IsQueueEmpty()) return false; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -279,6 +288,9 @@ NetworkHTTPRequest::~NetworkHTTPRequest() | ||||
| 		WinHttpCloseHandle(this->request); | ||||
| 		WinHttpCloseHandle(this->connection); | ||||
| 	} | ||||
|  | ||||
| 	std::lock_guard<std::mutex> lock(_http_callback_mutex); | ||||
| 	_http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end()); | ||||
| } | ||||
|  | ||||
| /* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data) | ||||
| @@ -292,6 +304,25 @@ NetworkHTTPRequest::~NetworkHTTPRequest() | ||||
|  | ||||
| /* static */ void NetworkHTTPSocketHandler::HTTPReceive() | ||||
| { | ||||
| 	/* Process all callbacks. */ | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock(_http_callback_mutex); | ||||
|  | ||||
| 		{ | ||||
| 				std::lock_guard<std::mutex> lock(_new_http_callback_mutex); | ||||
| 				if (!_new_http_callbacks.empty()) { | ||||
| 						/* We delay adding new callbacks, as HandleQueue() below might add a new callback. */ | ||||
| 						_http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end()); | ||||
| 						_new_http_callbacks.clear(); | ||||
| 				} | ||||
| 		} | ||||
|  | ||||
| 		for (auto &callback : _http_callbacks) { | ||||
| 				callback->HandleQueue(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* Process all requests. */ | ||||
| 	{ | ||||
| 		std::lock_guard<std::mutex> lock(_new_http_requests_mutex); | ||||
| 		if (!_new_http_requests.empty()) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user