From 8aa12ca468078ca58c0d46ef649a77c67abbfb1c Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Mon, 18 Sep 2023 11:38:34 +0900 Subject: [PATCH] taskschduler: fix a regression deadlock issue This fix introduces a workaround to enforce synchronous tasking on worker threads. Sometimes, out of threads get stuck in a deadlock condition. @Issue: https://github.com/thorvg/thorvg/issues/1636 --- src/loaders/lottie/tvgLottieBuilder.cpp | 7 +++++ src/loaders/svg/tvgSvgSceneBuilder.cpp | 14 +++++++++- src/renderer/tvgTaskScheduler.cpp | 34 +++++++++++-------------- src/renderer/tvgTaskScheduler.h | 1 + 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index fcd9e373..b8f0abfc 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -25,6 +25,7 @@ #include "tvgShape.h" #include "tvgLottieModel.h" #include "tvgLottieBuilder.h" +#include "tvgTaskScheduler.h" /************************************************************************/ @@ -577,11 +578,17 @@ static void _updateImage(LottieGroup* parent, LottieImage* image, int32_t frameN if (!picture) { picture = Picture::gen().release(); + + //force to load a picture on the same thread + TaskScheduler::async(false); + if (image->size > 0) { if (picture->load((const char*)image->b64Data, image->size, image->mimeType, false) != Result::Success) return; } else { if (picture->load(image->path) != Result::Success) return; } + + TaskScheduler::async(true); } if (ctx.propagator) { diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 567f9a22..2e018e27 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -550,12 +550,15 @@ static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mim return false; } +#include "tvgTaskScheduler.h" static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath) { if (!node->node.image.href) return nullptr; auto picture = Picture::gen(); + TaskScheduler::async(false); //force to load a picture on the same thread + const char* href = node->node.image.href; if (!strncmp(href, "data:", sizeof("data:") - 1)) { href += sizeof("data:") - 1; @@ -567,12 +570,14 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* auto size = b64Decode(href, strlen(href), &decoded); if (picture->load(decoded, size, mimetype, false) != Result::Success) { free(decoded); + TaskScheduler::async(true); return nullptr; } } else { auto size = svgUtilURLDecode(href, &decoded); if (picture->load(decoded, size, mimetype, false) != Result::Success) { free(decoded); + TaskScheduler::async(true); return nullptr; } } @@ -584,6 +589,7 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* const char *dot = strrchr(href, '.'); if (dot && !strcmp(dot, ".svg")) { TVGLOG("SVG", "Embedded svg file is disabled."); + TaskScheduler::async(true); return nullptr; } string imagePath = href; @@ -591,9 +597,14 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* auto last = svgPath.find_last_of("/"); imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath; } - if (picture->load(imagePath) != Result::Success) return nullptr; + if (picture->load(imagePath) != Result::Success) { + TaskScheduler::async(true); + return nullptr; + } } + TaskScheduler::async(true); + float w, h; Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { @@ -605,6 +616,7 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* picture->transform(m); _applyComposition(loaderData, picture.get(), node, vBox, svgPath); + return picture; } diff --git a/src/renderer/tvgTaskScheduler.cpp b/src/renderer/tvgTaskScheduler.cpp index 9dec68c0..45b93cad 100644 --- a/src/renderer/tvgTaskScheduler.cpp +++ b/src/renderer/tvgTaskScheduler.cpp @@ -100,17 +100,18 @@ struct TaskQueue { }; +static thread_local bool _async = true; //toggle async tasking for each thread on/off + + struct TaskSchedulerImpl { uint32_t threadCnt; vector threads; vector taskQueues; atomic idx{0}; - thread::id tid; TaskSchedulerImpl(unsigned threadCnt) : threadCnt(threadCnt), taskQueues(threadCnt) { - tid = this_thread::get_id(); threads.reserve(threadCnt); for (unsigned i = 0; i < threadCnt; ++i) { @@ -146,24 +147,13 @@ struct TaskSchedulerImpl void request(Task* task) { //Async - if (threadCnt > 0) { - auto tid = this_thread::get_id(); - if (tid == this->tid) { - task->prepare(); - auto i = idx++; - for (unsigned n = 0; n < threadCnt; ++n) { - if (taskQueues[(i + n) % threadCnt].tryPush(task)) return; - } - taskQueues[i % threadCnt].push(task); - //Not thread-safety now, it's requested from a worker-thread - } else { - for (unsigned i = 0; i < threadCnt; ++i) { - if (tid == threads[i].get_id()) { - task->prepare(); - (*task)(i + 1); - } - } + if (threadCnt > 0 && _async) { + task->prepare(); + auto i = idx++; + for (unsigned n = 0; n < threadCnt; ++n) { + if (taskQueues[(i + n) % threadCnt].tryPush(task)) return; } + taskQueues[i % threadCnt].push(task); //Sync } else { task->run(0); @@ -205,3 +195,9 @@ unsigned TaskScheduler::threads() if (inst) return inst->threadCnt; return 0; } + + +void TaskScheduler::async(bool on) +{ + _async = on; +} \ No newline at end of file diff --git a/src/renderer/tvgTaskScheduler.h b/src/renderer/tvgTaskScheduler.h index 0c6770d3..5d79fa54 100644 --- a/src/renderer/tvgTaskScheduler.h +++ b/src/renderer/tvgTaskScheduler.h @@ -38,6 +38,7 @@ struct TaskScheduler static void init(unsigned threads); static void term(); static void request(Task* task); + static void async(bool on); }; struct Task