Allow sync HTTP fetches to be interrupted to fix hanging (#14412)

Co-authored-by: Jude Melton-Houghton <jwmhjwmh@gmail.com>
This commit is contained in:
grorp 2024-03-12 20:09:43 +01:00
parent 5715434d5e
commit 4183443f02
6 changed files with 56 additions and 11 deletions

@ -608,13 +608,15 @@ bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
fetch_request.caller = HTTPFETCH_SYNC; fetch_request.caller = HTTPFETCH_SYNC;
fetch_request.timeout = std::max(MIN_HTTPFETCH_TIMEOUT, fetch_request.timeout = std::max(MIN_HTTPFETCH_TIMEOUT,
(long)g_settings->getS32("curl_file_download_timeout")); (long)g_settings->getS32("curl_file_download_timeout"));
httpfetch_sync(fetch_request, fetch_result); bool completed = httpfetch_sync_interruptible(fetch_request, fetch_result);
if (!fetch_result.succeeded) { if (!completed || !fetch_result.succeeded) {
target_file.close(); target_file.close();
fs::DeleteSingleFileOrEmptyDirectory(target); fs::DeleteSingleFileOrEmptyDirectory(target);
return false; return false;
} }
// TODO: directly stream the response data into the file instead of first
// storing the complete response in memory
target_file << fetch_result.data; target_file << fetch_result.data;
return true; return true;

@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "exceptions.h" #include "exceptions.h"
#include "debug.h" #include "debug.h"
#include "log.h" #include "log.h"
#include "porting.h"
#include "util/container.h" #include "util/container.h"
#include "util/thread.h" #include "util/thread.h"
#include "version.h" #include "version.h"
@ -783,7 +784,7 @@ static void httpfetch_request_clear(u64 caller)
} }
} }
void httpfetch_sync(const HTTPFetchRequest &fetch_request, static void httpfetch_sync(const HTTPFetchRequest &fetch_request,
HTTPFetchResult &fetch_result) HTTPFetchResult &fetch_result)
{ {
// Create ongoing fetch data and make a cURL handle // Create ongoing fetch data and make a cURL handle
@ -796,6 +797,28 @@ void httpfetch_sync(const HTTPFetchRequest &fetch_request,
fetch_result = *ongoing.complete(res); fetch_result = *ongoing.complete(res);
} }
bool httpfetch_sync_interruptible(const HTTPFetchRequest &fetch_request,
HTTPFetchResult &fetch_result, long interval)
{
if (Thread *thread = Thread::getCurrentThread()) {
HTTPFetchRequest req = fetch_request;
req.caller = httpfetch_caller_alloc_secure();
httpfetch_async(req);
do {
if (thread->stopRequested()) {
httpfetch_caller_free(req.caller);
fetch_result = HTTPFetchResult(fetch_request);
return false;
}
sleep_ms(interval);
} while (!httpfetch_async_get(req.caller, fetch_result));
httpfetch_caller_free(req.caller);
} else {
httpfetch_sync(fetch_request, fetch_result);
}
return true;
}
#else // USE_CURL #else // USE_CURL
/* /*
@ -825,13 +848,14 @@ static void httpfetch_request_clear(u64 caller)
{ {
} }
void httpfetch_sync(const HTTPFetchRequest &fetch_request, bool httpfetch_sync_interruptible(const HTTPFetchRequest &fetch_request,
HTTPFetchResult &fetch_result) HTTPFetchResult &fetch_result, long interval)
{ {
errorstream << "httpfetch_sync: unable to fetch " << fetch_request.url errorstream << "httpfetch_sync_interruptible: unable to fetch " << fetch_request.url
<< " because USE_CURL=0" << std::endl; << " because USE_CURL=0" << std::endl;
fetch_result = HTTPFetchResult(fetch_request); // sets succeeded = false etc. fetch_result = HTTPFetchResult(fetch_request); // sets succeeded = false etc.
return false;
} }
#endif // USE_CURL #endif // USE_CURL

@ -135,6 +135,9 @@ u64 httpfetch_caller_alloc_secure();
// to stop any ongoing fetches for the given caller. // to stop any ongoing fetches for the given caller.
void httpfetch_caller_free(u64 caller); void httpfetch_caller_free(u64 caller);
// Performs a synchronous HTTP request. This blocks and therefore should // Performs a synchronous HTTP request that is interruptible if the current
// only be used from background threads. // thread is a Thread object. interval is the completion check interval in ms.
void httpfetch_sync(const HTTPFetchRequest &fetch_request, HTTPFetchResult &fetch_result); // This blocks and therefore should only be used from background threads.
// Returned is whether the request completed without interruption.
bool httpfetch_sync_interruptible(const HTTPFetchRequest &fetch_request,
HTTPFetchResult &fetch_result, long interval = 100);

@ -114,9 +114,9 @@ int ModApiHttp::l_http_fetch_sync(lua_State *L)
infostream << "Mod performs HTTP request with URL " << req.url << std::endl; infostream << "Mod performs HTTP request with URL " << req.url << std::endl;
HTTPFetchResult res; HTTPFetchResult res;
httpfetch_sync(req, res); bool completed = httpfetch_sync_interruptible(req, res);
push_http_fetch_result(L, res, true); push_http_fetch_result(L, res, completed);
return 1; return 1;
} }

@ -60,6 +60,9 @@ DEALINGS IN THE SOFTWARE.
#endif #endif
thread_local Thread *current_thread = nullptr;
Thread::Thread(const std::string &name) : Thread::Thread(const std::string &name) :
m_name(name), m_name(name),
m_request_stop(false), m_request_stop(false),
@ -176,6 +179,8 @@ void Thread::threadProc(Thread *thr)
thr->m_kernel_thread_id = thread_self(); thr->m_kernel_thread_id = thread_self();
#endif #endif
current_thread = thr;
thr->setName(thr->m_name); thr->setName(thr->m_name);
g_logger.registerThread(thr->m_name); g_logger.registerThread(thr->m_name);
@ -196,6 +201,12 @@ void Thread::threadProc(Thread *thr)
} }
Thread *Thread::getCurrentThread()
{
return current_thread;
}
void Thread::setName(const std::string &name) void Thread::setName(const std::string &name)
{ {
#if defined(__linux__) #if defined(__linux__)

@ -119,6 +119,11 @@ public:
*/ */
bool setPriority(int prio); bool setPriority(int prio);
/*
* Returns the thread object of the current thread if it exists.
*/
static Thread *getCurrentThread();
/* /*
* Sets the currently executing thread's name to where supported; useful * Sets the currently executing thread's name to where supported; useful
* for debugging. * for debugging.