From bcc46f0389efe8528b76497b64954a28e965957f Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Fri, 1 Jan 2021 09:46:52 -0800 Subject: [PATCH] workaround limitations of moving std::function --- src/evhelper.cpp | 12 ++++++--- src/evhelper.h | 68 +++++++++++++++++++++++++++++++++++++++++++----- test/testev.cpp | 25 +++++++++++++++++- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/evhelper.cpp b/src/evhelper.cpp index 31f23e1..e1bcfdb 100644 --- a/src/evhelper.cpp +++ b/src/evhelper.cpp @@ -44,6 +44,10 @@ namespace pvxs {namespace impl { DEFINE_LOGGER(logerr, "pvxs.loop"); +namespace mdetail { +VFunctor0::~VFunctor0() {} +} + static epicsThreadOnceId evthread_once = EPICS_THREAD_ONCE_INIT; @@ -108,10 +112,10 @@ struct evbase::Pvt : public epicsThreadRunable std::weak_ptr internal_self; struct Work { - std::function fn; + mfunction fn; std::exception_ptr *result; epicsEvent *notify; - Work(std::function&& fn, std::exception_ptr *result, epicsEvent *notify) + Work(mfunction&& fn, std::exception_ptr *result, epicsEvent *notify) :fn(std::move(fn)), result(result), notify(notify) {} }; @@ -269,7 +273,7 @@ void evbase::sync() const call([](){}); } -bool evbase::_dispatch(std::function&& fn, bool dothrow) const +bool evbase::_dispatch(mfunction&& fn, bool dothrow) const { bool empty; { @@ -290,7 +294,7 @@ bool evbase::_dispatch(std::function&& fn, bool dothrow) const return true; } -bool evbase::_call(std::function&& fn, bool dothrow) const +bool evbase::_call(mfunction&& fn, bool dothrow) const { if(pvt->worker.isCurrentThread()) { fn(); diff --git a/src/evhelper.h b/src/evhelper.h index 218131d..0c41c41 100644 --- a/src/evhelper.h +++ b/src/evhelper.h @@ -59,6 +59,60 @@ struct owned_ptr : public std::unique_ptr } }; +/* It seems that std::function(Fn&&) from gcc (circa 8.3) and clang (circa 7.0) + * always copies the functor/lambda. We can't allow this when transferring ownership + * of shared_ptr<> instances to a worker thread as it leaves the caller thread with a + * reference. + * + * std::unique_ptr arg{new int(42)}; + * // eg. with + * auto lambda = [arg{std::move(arg)}]() { // c++14 capture w/ move + * auto trash(std::move(arg)); + * }; + * // or + * auto lambda = std::bind([](std::unique_ptr& arg) { // c++11 capture w/ move + * auto trash(std::move(arg)); + * }, std::move(arg)); + * // the following line tries to copy the unique_ptr which fails to compile + * std::function fn(std::move(lambda)); + * + * So we invent our own limited, non-copyable, version of std::function. + */ +namespace mdetail { +struct PVXS_API VFunctor0 { + virtual ~VFunctor0() =0; + virtual void invoke() =0; +}; +template +struct Functor0 : public VFunctor0 { + Functor0() = default; + Functor0(const Functor0&) = delete; + Functor0(Functor0&&) = default; + Functor0(Fn&& fn) : fn(std::move(fn)) {} + virtual ~Functor0() {} + + void invoke() override final { fn(); } +private: + Fn fn; +}; +} // namespace detail + +struct mfunction { + mfunction() = default; + template + mfunction(Fn&& fn) + :fn{new mdetail::Functor0(std::move(fn))} + {} + void operator()() const { + fn->invoke(); + } + explicit operator bool() const { + return fn.operator bool(); + } +private: + std::unique_ptr fn; +}; + struct PVXS_API evbase { evbase() = default; explicit evbase(const std::string& name, unsigned prio=0); @@ -71,31 +125,31 @@ struct PVXS_API evbase { void sync() const; private: - bool _dispatch(std::function&& fn, bool dothrow) const; - bool _call(std::function&& fn, bool dothrow) const; + bool _dispatch(mfunction&& fn, bool dothrow) const; + bool _call(mfunction&& fn, bool dothrow) const; public: // queue request to execute in event loop. return after executed. inline - void call(std::function&& fn) const { + void call(mfunction&& fn) const { _call(std::move(fn), true); } inline - bool tryCall(std::function&& fn) const { + bool tryCall(mfunction&& fn) const { return _call(std::move(fn), false); } // queue request to execute in event loop. return immediately. inline - void dispatch(std::function&& fn) const { + void dispatch(mfunction&& fn) const { _dispatch(std::move(fn), true); } inline - bool tryDispatch(std::function&& fn) const { + bool tryDispatch(mfunction&& fn) const { return _dispatch(std::move(fn), false); } - bool tryInvoke(bool docall, std::function&& fn) const { + bool tryInvoke(bool docall, mfunction&& fn) const { if(docall) return tryCall(std::move(fn)); else diff --git a/test/testev.cpp b/test/testev.cpp index 16f2fae..f944294 100644 --- a/test/testev.cpp +++ b/test/testev.cpp @@ -63,6 +63,29 @@ void test_call() testFail("Caught wrong exception : %s \"%s\"", typeid(e).name(), e.what()); } + { + testDiag("Demonstrate exclusive ownership transfer"); + bool called = false; + std::unique_ptr ival{new int(42)}; + base.call(std::bind([&called](std::unique_ptr& ival) { + auto trash(std::move(ival)); + called = true; + }, std::move(ival))); + testTrue(called); + } + + { + testDiag("Demonstrate shared ownership transfer"); + bool called = false; + auto ival(std::make_shared(42)); + base.call(std::bind([&called](std::shared_ptr& ival) { + auto trash(std::move(ival)); + testEq(trash.use_count(), 1u); + called = true; + }, std::move(ival))); + testTrue(called); + } + auto internal(base.internal()); base = evbase(); // loop stopped @@ -124,7 +147,7 @@ void test_fill_evbuf() MAIN(testev) { SockAttach attach; - testPlan(17); + testPlan(20); testSetup(); test_call(); test_fill_evbuf();