diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2016-12-30 04:39:26 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2016-12-30 04:39:26 +0100 |
commit | e712a6eea96d381d2f1e3c0f8328d870321b31de (patch) | |
tree | 2df5a79fb5d3b219f0fbeae68c41332608e98661 | |
parent | 07ef302c354369ffdecf7016207a01097c3cdc59 (diff) |
compat: prevent deadlock with race
We can't depend on cvar getting notified only after "src" runs out of
scope.
Now, in case signal destroyed() runs first:
- mtx locked
- flag set to true
- empty cvar notified
Thus, doesn't hang.
In case we wait first:
- mtx locked
- flag is false
- cvar notification arrives
Of course semaphore code always runs serially since they're covered by a
mutex. We have all our bases covered.
Previously the code never hung simply because the "curthread" condition
was always true.
I removed the "curthread" code paths since they don't add anything. Also
rvalue references got used incorrectly.
-rw-r--r-- | compat/run-in-thread.hpp | 70 |
1 files changed, 42 insertions, 28 deletions
diff --git a/compat/run-in-thread.hpp b/compat/run-in-thread.hpp index 90aa143b..ba99d0b9 100644 --- a/compat/run-in-thread.hpp +++ b/compat/run-in-thread.hpp @@ -16,10 +16,21 @@ template<typename t> struct run_in_thread_traits { using type = t; - using ret_type = t&&; + using ret_type = t; + static inline void assign(t& lvalue, const t& rvalue) { lvalue = rvalue; } + static inline t pass(const t& val) { return val; } + template<typename F> static inline t call(F&& fun) { return std::move(fun()); } +}; + +template<typename u> +struct run_in_thread_traits<u&&> +{ + using t = typename std::remove_reference<u>::type; + using type = t; + using ret_type = u; static inline void assign(t& lvalue, t&& rvalue) { lvalue = rvalue; } - static inline ret_type&& pass(ret_type&& val) { return std::move(val); } - template<typename F> static ret_type call(F& fun) { return std::move(fun()); } + static inline t&& pass(t&& val) { return val; } + template<typename F> static inline t&& call(F&& fun) { return std::move(fun()); } }; template<> @@ -29,7 +40,7 @@ struct run_in_thread_traits<void> using ret_type = void; static inline void assign(unsigned char&, unsigned char&&) {} static inline void pass(type&&) {} - template<typename F> static type call(F& fun) { fun(); return type(0); } + template<typename F> static type call(F&& fun) { fun(); return type(0); } }; } @@ -40,45 +51,48 @@ auto run_in_thread_sync(QObject* obj, F&& fun) { using lock_guard = std::unique_lock<std::mutex>; - std::mutex mtx; - lock_guard guard(mtx); - std::condition_variable cvar; - - std::thread::id waiting_thread = std::this_thread::get_id(); - using traits = qt_impl_detail::run_in_thread_traits<decltype(std::forward<F>(fun)())>; typename traits::type ret; - bool skip_wait = false; + struct semaphore final + { + std::mutex mtx; + std::condition_variable cvar; + bool flag; + + semaphore() : flag(false) {} + + void wait() + { + lock_guard guard(mtx); + while (!flag) + cvar.wait(guard); + } + + void notify() + { + lock_guard guard(mtx); + flag = true; + cvar.notify_one(); + } + }; + + semaphore sem; { QObject src; - QThread* t(obj->thread()); - assert(t); - src.moveToThread(t); QObject::connect(&src, &QObject::destroyed, obj, [&]() { - std::thread::id calling_thread = std::this_thread::get_id(); - if (waiting_thread == calling_thread) - { - skip_wait = true; - traits::assign(ret, traits::call(fun)); - } - else - { - lock_guard guard(mtx); - traits::assign(ret, traits::call(fun)); - cvar.notify_one(); - } + traits::assign(ret, traits::call(fun)); + sem.notify(); }, Qt::AutoConnection); } - if (!skip_wait) - cvar.wait(guard); + sem.wait(); return traits::pass(std::move(ret)); } |