From 7b25609896be125db2384815c9888ae7d3f746cb Mon Sep 17 00:00:00 2001
From: Ichthyostega
Date: Wed, 11 Oct 2023 21:06:56 +0200
Subject: [PATCH] Library: test coverage for self-managed thread
...extract and improve the tuple-rewriting function
...improve instance tracking test dummy objects
...complete test coverage and verify proper memory handling
---
src/lib/meta/function.hpp | 50 ++++-
src/lib/test/test-helper.cpp | 2 +
src/lib/test/testdummy.hpp | 116 ++++++++++-
src/lib/thread.hpp | 32 +--
tests/11concurrency.tests | 2 +-
tests/12metaprogramming.tests | 5 +
.../library/meta/late-bind-instance-test.cpp | 196 ++++++++++++++++++
.../thread-wrapper-autonomous-test.cpp | 24 +--
wiki/thinkPad.ichthyo.mm | 135 ++++++------
9 files changed, 449 insertions(+), 113 deletions(-)
create mode 100644 tests/library/meta/late-bind-instance-test.cpp
diff --git a/src/lib/meta/function.hpp b/src/lib/meta/function.hpp
index 13ae3d097..c64be5f23 100644
--- a/src/lib/meta/function.hpp
+++ b/src/lib/meta/function.hpp
@@ -205,6 +205,54 @@ namespace meta{
+ /** Placeholder marker for a special argument position to be supplied later */
+ template
+ struct InstancePlaceholder { };
+
+ namespace {// Helper to inject instance-pointer instead of placeholder...
+ /**
+ * @internal helper to detect an InstancePlaceholder
+ * @return an instance-pointer, statically casted to the marked target type.
+ */
+ template
+ constexpr inline TAR*
+ maybeInject (W& instance, InstancePlaceholder)
+ {
+ return static_cast (&instance);
+ }
+
+ /** (default case: fall-through) */
+ template
+ constexpr inline X
+ maybeInject (W&, X&& x)
+ {
+ return std::move(x);
+ }
+ }//(End)Helper for lateBindInstance.
+
+ /**
+ * Fix-up the arguments for a member-function invocation,
+ * allowing to inject the actual `this` instance into an existing argument sequence.
+ * @remark invocation of a member function requires to supply the _object instance_ as
+ * first element in the argument list; sometimes this poses a design challenge,
+ * since the actual instance may not be known at the point where the other arguments
+ * are prepared. As a remedy, the position of the instance pointer can be marked with
+ * the \ref InstancePlaceholder, allowing to splice in the actual pointer when known.
+ */
+ template
+ constexpr inline auto
+ lateBindInstance (W& instance, TUP&& invocation)
+ {
+ auto splice = [&instance](auto&& ...xs)
+ {
+ return std::tuple{maybeInject (instance, std::move(xs))...};
+ };
+ return std::apply (splice, std::forward (invocation));
+ }
+
+
+
+
@@ -214,7 +262,7 @@ namespace meta{
* and the bare function signature #Sig
* @param RET the function return type
* @param ARGS a type sequence describing the arguments
- */
+ */ //////////////////////////////////////////////////////////////////////TICKET #987 : make lib::meta::Types variadic, then replace this by a single variadic template
template
struct FunctionTypedef;
diff --git a/src/lib/test/test-helper.cpp b/src/lib/test/test-helper.cpp
index 4681e4c53..ac1009dd1 100644
--- a/src/lib/test/test-helper.cpp
+++ b/src/lib/test/test-helper.cpp
@@ -49,6 +49,8 @@ namespace test{
long Dummy::_local_checksum = 0;
bool Dummy::_throw_in_ctor = false;
+ EventLog Tracker::log{"Instance-Tracker"};
+
string
diff --git a/src/lib/test/testdummy.hpp b/src/lib/test/testdummy.hpp
index 9de21a0c8..cbbe32cd4 100644
--- a/src/lib/test/testdummy.hpp
+++ b/src/lib/test/testdummy.hpp
@@ -22,23 +22,32 @@
/** @file testdummy.hpp
- ** unittest helper code: test dummy object.
- ** This dummy includes some distinct random identity marker
- ** plus a checksum facility to verify instance management, i.e.
- ** to verify each created instance was properly destroyed after use.
+ ** unittest helper code: test dummy objects to track instances.
+ ** These can be used to verify proper allocation handling, either by
+ ** watching the checksum of \ref Dummy, or by matching on the \ref EventLog
+ ** embedded into \ref Tracker.
*/
#include "lib/nocopy.hpp"
+#include "lib/test/event-log.hpp"
+#include "lib/format-string.hpp"
+#include "lib/util.hpp"
#include
+#include
namespace lib {
namespace test{
-
- class Dummy
+
+ /**
+ * A Dummy object for tests. Each instance includes some distinct
+ * random identity marker plus a checksum facility to verify instance management,
+ * i.e. to verify each created instance was properly destroyed after use.
+ */
+ class Dummy
: util::NonCopyable
{
int val_;
@@ -62,6 +71,7 @@ namespace test{
}
virtual long
+
acc (int i) ///< dummy API operation
{
return val_+i;
@@ -107,8 +117,100 @@ namespace test{
if (_throw_in_ctor)
throw val_;
}
-
};
+
+
+
+
+ /**
+ * A tracking Dummy object for tests.
+ * All construction-, destruction and copy operations
+ * are logged into an embedded lib::test::EventLog
+ */
+ struct Tracker
+ {
+ static lib::test::EventLog log;
+
+ static constexpr int DEFUNCT = std::numeric_limits::min();
+ static constexpr int DEAD = std::numeric_limits::max();
+
+ int val;
+
+ ~Tracker()
+ {
+ log.call (this,"dtor", val);
+ val = DEAD;
+ }
+
+ Tracker()
+ : val{rand() % 1000}
+ {
+ log.call (this,"ctor");
+ }
+
+ Tracker(int v)
+ : val{v}
+ {
+ log.call (this,"ctor", v);
+ }
+
+ Tracker(Tracker const& ol)
+ : val{ol.val}
+ {
+ log.call (this,"ctor-copy", ol);
+ }
+
+ Tracker(Tracker && oo)
+ : val{oo.val}
+ {
+ log.call (this,"ctor-move", oo);
+ oo.val = DEFUNCT;
+ }
+
+ Tracker&
+ operator= (Tracker const& ol)
+ {
+ if (util::isSameObject (*this, ol))
+ {
+ log.call (this, "self-assign-copy");
+ }
+ else
+ {
+ log.call (this, "assign-copy", ol);
+ val = ol.val;
+ }
+ return *this;
+ }
+
+ Tracker&
+ operator= (Tracker && oo)
+ {
+ if (util::isSameObject (*this, oo))
+ {
+ log.call (this, "self-assign-move");
+ }
+ else
+ {
+ log.call (this, "assign-move", oo);
+ val = oo.val;
+ oo.val = DEFUNCT;
+ }
+ return *this;
+ }
+
+ operator string() const
+ {
+ return util::_Fmt{"Track{%02d}"} % val;
+ }
+
+ friend void
+ swap (Tracker& t1, Tracker& t2)
+ {
+ log.call ("static", "swap", t1, t2);
+ std::swap (t1.val, t2.val);
+ }
+ };
+
}} // namespace lib::test
diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp
index ceb52240d..874eef85d 100644
--- a/src/lib/thread.hpp
+++ b/src/lib/thread.hpp
@@ -150,6 +150,7 @@ namespace lib {
namespace thread {// Thread-wrapper base implementation...
+ using lib::meta::lateBindInstance;
using lib::meta::typeSymbol;
using lib::meta::_Fun;
using util::isnil;
@@ -360,27 +361,6 @@ namespace lib {
}
};
- template
- struct InstancePlaceholder { };
-
- template
- inline INVO
- lateBindInstance (W&, INVO invocation)
- {
- return invocation;
- }
-
- template
- inline auto
- lateBindInstance (W& instance, const tuple, ARGS...> invocation)
- {
- auto splice = [&instance](auto f, auto&, auto ...args)
- {
- TAR* instancePtr = static_cast (&instance);
- return tuple{move(f), instancePtr, move(args)...};
- };
- return std::apply (splice, invocation);
- }
/**
* Policy-based configuration of thread lifecycle
@@ -784,7 +764,7 @@ namespace lib {
{
using Launch = typename TAR::Launch;
launchDetached (Launch{std::move (memFun)
- ,thread::InstancePlaceholder{}
+ ,lib::meta::InstancePlaceholder{}
,forward (args)...
}
.threadID (threadID));
@@ -795,10 +775,10 @@ namespace lib {
inline void
launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args)
{
- return launchDetached (util::joinDash (lib::meta::typeSymbol(), args...)
- ,memFun
- ,forward (args)...
- );
+ launchDetached (util::joinDash (lib::meta::typeSymbol(), args...)
+ ,memFun
+ ,forward (args)...
+ );
}
diff --git a/tests/11concurrency.tests b/tests/11concurrency.tests
index 248c18f84..bb41d877f 100644
--- a/tests/11concurrency.tests
+++ b/tests/11concurrency.tests
@@ -42,7 +42,7 @@ return: 0
END
-PLANNED "Launch self-contained detached Thread" ThreadWrapperAutonomous_test <
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+* *****************************************************/
+
+/** @file late-bind-instance-test.cpp
+ ** unit test \ref LateBindInstance_test
+ */
+
+
+#include "lib/test/run.hpp"
+#include "lib/meta/function.hpp"
+#include "lib/meta/tuple-helper.hpp"
+#include "lib/test/testdummy.hpp"
+#include "lib/format-cout.hpp"
+#include "lib/format-util.hpp"
+#include "lib/test/diagnostic-output.hpp"
+#include "lib/rational.hpp"
+#include "lib/util.hpp"
+
+#include
+#include
+#include
+
+
+namespace lib {
+namespace meta {
+namespace test {
+
+ using std::move;
+ using std::rand;
+ using std::tuple;
+ using std::string;
+ using lib::meta::dump;
+ using lib::test::Tracker;
+
+
+
+
+
+
+ /**********************************************************************************//**
+ * @test verify a scheme to supply the actual instance for member function invocation.
+ * @remark in the end this is just a value replacement in a tuple, where the position
+ * is marked with a placeholder type. This scheme is used for starting threads,
+ * where the actual thread instance is not known yet at the point where the
+ * arguments for the thread-function are constructed
+ * @see lib::launchDetached
+ */
+ class LateBindInstance_test : public Test
+ {
+ void
+ run (Arg)
+ {
+ demonstrateUsage();
+ verify_forwarding();
+ verify_cornerCases();
+ }
+
+
+
+ /** @test demonstrate the usage of lib::meta::lateBindInstance():
+ * - construct an argument tuple
+ * - mark the position of the `this`-ptr with a placeholder
+ * - rewrite the arguments later when the actual instance is known
+ * - member function can now be invoked, since the instance was injected.
+ */
+ void
+ demonstrateUsage()
+ {
+ uint randomLimit = 2 + rand() % 98;
+
+ auto plannedArgs
+ = tuple{InstancePlaceholder{}
+ ,randomLimit
+ };
+
+ // the actual instance may be available only later...
+ Test& instanceRef = *this;
+
+ // now rewrite the argument tuple to inject the instance-ptr
+ auto preparedArgs = lateBindInstance (instanceRef, plannedArgs);
+
+ uint res = std::apply (&LateBindInstance_test::theMember, preparedArgs);
+ CHECK (res < randomLimit);
+ }
+
+ uint
+ theMember (uint limit)
+ {
+ return rand() % limit;
+ }
+
+
+
+ /** @test verify that the rewriting process does not incur unnecessary data copies
+ */
+ void
+ verify_forwarding()
+ {
+ auto& log = Tracker::log;
+ log.clear (this);
+
+ InstancePlaceholder marker;
+ Tracker t1(11);
+
+ log.event("construct tuple");
+ tuple tup(t1, marker, Tracker{23}, 55);
+
+ Test& instanceRef = *this;
+
+ log.event("invoke lateBindInstance");
+ auto tup1r = lateBindInstance (instanceRef, tup);
+ log.event("got result");
+
+ // before invocation the Tracker instances are created (obviously..)
+ CHECK (log.verifyCall("ctor").arg(11)
+ .beforeCall("ctor-copy").arg("Track{11}")
+ .beforeEvent("invoke lateBindInstance"));
+ CHECK (log.verifyCall("ctor").arg(23)
+ .beforeCall("ctor-move").arg("Track{23}")
+ .beforeCall("dtor").arg(Tracker::DEFUNCT)
+ .beforeEvent("invoke lateBindInstance"));
+
+ // but there is no copy operation after the invocation
+ CHECK (log.ensureNot("ctor-copy")
+ .afterEvent("invoke lateBindInstance"));
+
+ // both Tracker instances are moved two times
+ // - once to create the maybeInject-invocation and
+ // - once to consolidate the result
+ // for each instance one moved-away temporary is destroyed
+ CHECK (log.verifyEvent("invoke lateBindInstance")
+ .beforeCall("ctor-move").arg("Track{11}")
+ .beforeCall("ctor-move").arg("Track{11}")
+ .beforeCall("dtor").arg(Tracker::DEFUNCT)
+ .beforeEvent("got result"));
+ CHECK (log.verifyEvent("invoke lateBindInstance")
+ .beforeCall("ctor-move").arg("Track{23}")
+ .beforeCall("ctor-move").arg("Track{23}")
+ .beforeCall("dtor").arg(Tracker::DEFUNCT)
+ .beforeEvent("got result"));
+
+ cout << "____Tracker-Log_______________\n"
+ << util::join(Tracker::log, "\n")
+ << "\n───╼━━━━━━━━━━━╾──────────────"< marker;
+ CHECK (dump(lateBindInstance (dummy, tuple<>{})) == "()"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{42})) == "(42)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{1,2,3})) == "(1,2,3)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{marker,2,3})) == "(«long»,2,3)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{1,marker,3})) == "(1,«long»,3)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{1,2,marker})) == "(1,2,«long»)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{marker})) == "(«long»)"_expect);
+ CHECK (dump(lateBindInstance (dummy, tuple{string{"1"}
+ ,"2"
+ ,marker
+ ,tuple(2,3)
+ , 5.5
+ })) == "(1,2,«long»,«tuple»──(2,3),5.5)"_expect);
+ }
+ };
+
+
+ /** Register this test class... */
+ LAUNCHER (LateBindInstance_test, "unit meta");
+
+
+
+}}} // namespace lib::meta::test
diff --git a/tests/library/thread-wrapper-autonomous-test.cpp b/tests/library/thread-wrapper-autonomous-test.cpp
index 090adffd1..3dd5ec598 100644
--- a/tests/library/thread-wrapper-autonomous-test.cpp
+++ b/tests/library/thread-wrapper-autonomous-test.cpp
@@ -28,26 +28,18 @@
#include "lib/test/run.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/thread.hpp"
-#include "lib/test/diagnostic-output.hpp"///////////////////TODO
#include
#include
using test::Test;
-using std::atomic_uint;
-using std::this_thread::yield;
+using std::atomic_bool;
using std::this_thread::sleep_for;
using namespace std::chrono_literals;
namespace lib {
namespace test{
-
- namespace { // test parameters
-
- const uint NUM_THREADS = 200;
- const uint REPETITIONS = 10;
- }
/*******************************************************************//**
@@ -71,11 +63,11 @@ namespace test{
void
demonstrateSimpleUsage()
{
- atomic_uint i{0};
- launchDetached ("anarchy", [&]{ ++i; });
+ atomic_bool didRun{false};
+ launchDetached ("anarchy", [&]{ didRun = true; });
sleep_for(1ms);
- CHECK (i == 1); // verify the effect has taken place
+ CHECK (didRun); // verify the effect has taken place
}
@@ -96,17 +88,17 @@ namespace test{
doIt (int extra)
{
watcher.setVal (extra);
-SHOW_EXPR(extra)
sleep_for (5ms);
-SHOW_EXPR("bye")
}
};
- //
+ // Note the Dummy member allows to watch instance lifecycle
CHECK (0 == Dummy::checksum());
- //
+
launchDetached (&TestThread::doIt, 55);
CHECK (0 < Dummy::checksum());
+ sleep_for (1ms);
+ CHECK (55 == Dummy::checksum());
sleep_for (10ms);
CHECK (0 == Dummy::checksum());
}
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 0e371ae9f..f48717de4 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -65187,7 +65187,7 @@
-
+
@@ -65348,13 +65348,12 @@
-
+
+
-
-
-
+
denn: grade hier für den »Autonomous Thread« kommt man von außen nicht an die Addresse des Objektes ran
@@ -65371,60 +65370,50 @@
-
-
+
-
+
-
-
-
+
Wenn wir es irgendwie schaffen könnten, das Objekt schon auf den Heap zu allozieren, aber zu verhindern, daß es „fliegt“ (bzw. wirklich konstruiert wird). Dann wüßten wir die Addresse. Danach würden wir ein placement-New in die Allokation machen, und könnten diesem tatsächlichen Konstruktor-Aufruf ganz verträumt den richtigen Instanz-Pointer mitgeben. Vorraussetzung wäre allerdings, daß sich diese manipulierte Allokation später ganz gewöhnlich per operator delete wieder entfernen ließe. Das wäre dann entweder ein fragiler Hack, der sich auf Plattform-Interna abstützt, oder es wäre ein custom-operator-new im Stil der 90er-Jahre
-
-
+
-<html>
- <head>
-
-
- </head>
- <body>
- <p>
- Dafür bräuchte ich eine Stelle, an der die tatsächliche Instanz bereits
+
+
+
+
+ Dafür bräuchte ich eine Stelle, an der die tatsächliche Instanz bereits
bekannt ist, aber die Invocation nochin generischer Form vorliegt. In
- einem solchen Kontext könnte man dann einen Marker-Typ erkennen und an
- die tatsächliche Instanz binden.
- </p>
- <p>
+ einem solchen Kontext könnte man dann einen Marker-Typ erkennen und an
+ die tatsächliche Instanz binden.
+
+
- </p>
- <p>
- <u>�� Und in der Tat</u>: diesen Kontext gäbe es in der
+
+
+ Und in der Tat: diesen Kontext gäbe es in der
Hilfsfunktion buildLauncher()
- </p>
- </body>
-</html>
-
+
+
+
-
-
-
+
-
@@ -65438,13 +65427,12 @@
-
-
+
-
+
@@ -65457,6 +65445,33 @@
+
+
+
+
+
+
+
+
+
+
+ Es gibt keinen Grund, die Ersetzung nur an 2.Stelle im Tupel zu machen; auch ist es nicht notwendig, die Typen irgendwie festzunageln. Allein die Gegenwart des Marker-Typs genügt, denn diesen setzt man nur explizit ein. Damit wäre diese Funktion auch auf reine Argument-Tupel anwendbar, nicht nur (wie hier) auf Invocation-Tupel (functor, args...)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -65877,44 +65892,35 @@
-
-
-
+
allerdings: abgeleitete Klassen leben gefährlich
-
-
+
-
-
-
+
Ein normaler Thread (und ein Joinable) würden den Aufruf des Destruktors verbieten, und damit die Applikation terminieren
-
-
+
-
-
-
+
aber der Thread ist schon „unterwegs“
-
-
+
@@ -65923,16 +65929,13 @@
-
-
-
+
...aber das gibt mein Framework nicht her
-
-
+
@@ -66301,14 +66304,22 @@
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+