From 685be1b0393373aedfa25fa17731c46021e2183e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 15 Oct 2023 20:42:55 +0200 Subject: [PATCH] Library/Application: consolidate Monitor API and usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is Step-2 : change the API towards application Notably all invocation variants to support member functions or a reference to bool flags are retracted, since today a λ-binding directly at usage site tends to be more readable. The function names are harmonised with the C++ standard and emergency shutdown in the Subsystem-Runner is rationalised. The old thread-wrapper test is repurposed to demonstrate the effectiveness of monitor based locking. --- src/common/advice/advice.cpp | 12 +- src/common/guifacade.cpp | 4 +- src/common/subsystem-runner.hpp | 145 +++---- src/lib/allocation-cluster.cpp | 8 +- src/lib/call-queue.hpp | 6 +- src/lib/symbol-table.hpp | 2 +- src/lib/sync.hpp | 88 ++-- src/lib/typed-counter.hpp | 2 +- src/steam/assetmanager.cpp | 2 +- src/steam/control/command-registry.hpp | 8 +- src/steam/control/looper.hpp | 11 +- src/steam/control/steam-dispatcher.cpp | 57 ++- .../mobject/session/sess-manager-impl.cpp | 10 +- src/steam/play/output-director.cpp | 4 +- src/steam/play/play-service.cpp | 4 +- src/vault/enginefacade.cpp | 2 +- src/vault/netnodefacade.cpp | 2 +- src/vault/scriptrunnerfacade.cpp | 2 +- tests/basics/call-queue-test.cpp | 2 +- .../steam/control/dispatcher-looper-test.cpp | 22 +- .../library/sync-barrier-performance-test.cpp | 6 +- tests/library/sync-classlock-test.cpp | 2 +- tests/library/sync-locking-test.cpp | 6 +- tests/library/sync-locking-test_2.cpp | 112 +++--- tests/library/sync-timedwait-test.cpp | 8 +- tests/library/sync-waiting-test.cpp | 106 +---- tests/stage/bus-term-test.cpp | 4 +- tests/vault/gear/work-force-test.cpp | 4 +- wiki/thinkPad.ichthyo.mm | 380 ++++++++++++------ 29 files changed, 544 insertions(+), 477 deletions(-) diff --git a/src/common/advice/advice.cpp b/src/common/advice/advice.cpp index 2ce5820f3..649aecdab 100644 --- a/src/common/advice/advice.cpp +++ b/src/common/advice/advice.cpp @@ -174,7 +174,7 @@ namespace advice { void manageAdviceData (PointOfAdvice* entry, DeleterFunc* how_to_delete) { - Lock sync (this); + Lock sync{this}; adviceDataRegistry_.manage (entry, how_to_delete); } @@ -196,14 +196,14 @@ namespace advice { publishRequestBindingChange(PointOfAdvice & req, HashVal previous_bindingKey) { - Lock sync (this); + Lock sync{this}; index_.modifyRequest(previous_bindingKey, req); } void registerRequest(PointOfAdvice & req) { - Lock sync (this); + Lock sync{this}; index_.addRequest (req); } @@ -212,7 +212,7 @@ namespace advice { { try { - Lock sync (this); + Lock sync{this}; index_.removeRequest (req); } @@ -227,7 +227,7 @@ namespace advice { void publishProvision (PointOfAdvice* newProvision, const PointOfAdvice* previousProvision) { - Lock sync (this); + Lock sync{this}; if (!previousProvision && newProvision) index_.addProvision (*newProvision); @@ -244,7 +244,7 @@ namespace advice { void discardSolutions (const PointOfAdvice* existingProvision) { - Lock sync (this); + Lock sync{this}; if (existingProvision) index_.removeProvision (*existingProvision); diff --git a/src/common/guifacade.cpp b/src/common/guifacade.cpp index 0efed9e21..02e3259df 100644 --- a/src/common/guifacade.cpp +++ b/src/common/guifacade.cpp @@ -118,7 +118,7 @@ namespace stage { bool start (lumiera::Option&, Subsys::SigTerm termNotification) override { - Lock guard (this); + Lock guard{this}; if (facade) return false; // already started @@ -159,7 +159,7 @@ namespace stage { void closeGuiModule () { - Lock guard (this); + Lock guard{this}; if (!facade) { WARN (guifacade, "Termination signal invoked, but GUI is currently closed. " diff --git a/src/common/subsystem-runner.hpp b/src/common/subsystem-runner.hpp index 6f72fdac6..a07059855 100644 --- a/src/common/subsystem-runner.hpp +++ b/src/common/subsystem-runner.hpp @@ -50,167 +50,162 @@ #include "lib/error.hpp" #include "lib/util.hpp" #include "lib/util-foreach.hpp" +#include "lib/format-string.hpp" #include "common/subsys.hpp" #include "lib/sync.hpp" -#include #include #include namespace lumiera { - using std::bind; - using std::function; - using std::placeholders::_1; + using lib::Sync; + using lib::RecursiveLock_Waitable; + using std::chrono_literals::operator ""s; using std::vector; using std::string; + using util::_Fmt; using util::cStr; using util::isnil; using util::and_all; using util::for_each; using util::removeall; - using lib::Sync; - using lib::RecursiveLock_Waitable; namespace { - - /** limit waiting for subsystem shutdown in case of - * an emergency shutdown to max 2 seconds */ - const uint EMERGENCYTIMEOUT = 2000; - - - function - isRunning() { - return bind (&Subsys::isRunning, _1); - } + /** limited wait period for unwinding of remaining subsystems + * in case of an emergency shutdown, to avoid deadlock */ + const auto EMERGENCY_STOP = 5s; } + /*************************************************************************//** * Implementation helper for managing execution of a collection of subsystems, * which may depend on one another and execute in parallel. Properties of the * subsystems are available through Subsys object refs, which act as handle. - * In this context, "Subsystem" is an \em abstraction and doesn't necessarily + * In this context, »Subsystem« is an _abstraction_ and doesn't necessarily * correspond to a single component, interface or plugin. It may well be a * complete layer of the application (e.g. the GUI). * * # Protocol of operation * The SubsystemRunner is to be configured with a lumiera::Option object first. - * Then, primary subsystems are \link #maybeRun provided \endlink for eventual - * startup, which may depend on conditions defined by the subsystem. When - * a component is actually to be pulled up, all of its prerequisite subsystems - * shall be started in advance. Problems while starting may result in throwing - * an exception, which is \em not handled here and aborts the whole operation. - * On startup, a signal slot is reserved for each subsystem to notify the - * SubsystemRunner on termination. It is the liability of the subsystems to - * ensure this signal is activated regardless of what actually causes the - * termination; failure to do so may deadlock the SubsystemRunner. + * Then, primary subsystems are [provided](\ref SubsystemRunner::maybeRun) for + * eventual startup, which may depend on conditions defined by the subsystem. + * When it turns out (by investigating the options) that a Subsystem is actually + * to be pulled up, all of its prerequisite subsystems shall be started beforehand. + * Problems while starting may result in throwing an exception, which is _not handled_ + * here and aborts the whole operation. On startup, a _callback signal slot_ is reserved + * for each subsystem to notify the SubsystemRunner on termination. It is the liability + * of the subsystems to ensure this callback functor is activated reliably, irrespective + * of what actually causes the termination; failure to do so may deadlock the whole System. * * Usually, the startup process is conducted from one (main) thread, which enters - * a blocking wait() after starting the subsystems. Awakened by some termination - * signal from one of the subsystems, termination of any remaining subsystems - * will be triggered. The #wait() function returns after shutdown of all subsystems, - * signalling an emergency exit (caused by an exception) with its return value. + * the [blocking wait](\ref SubsystemRunner::wait) after starting the subsystems. + * Awakened by some termination signal from one of the subsystems, termination of any + * remaining subsystems will be triggered. The #wait() function returns after shutdown + * of all subsystems, signalling an emergency situation with its return value. In this + * context, _emergency_ is defined by encountering an top-level exception in any + * Subsystem, reported by a non-empty error string in the #sigTerm handler. + * An _emergency_ thus jeopardises the ability to wind-down the all parts + * of the application reliably. * - * @todo 2018 this component works well but could be (re)written in a cleaner way + * @todo 2018 this component works well but could be (re)written in a cleaner way ////////////////////////TICKET #1177 * * @see lumiera::AppState * @see lumiera::Subsys - * @see main.cpp + * @see main.cpp */ class SubsystemRunner - : public Sync + : public Sync { Option& opts_; volatile bool emergency_; vector running_; - function start_, - stopIt_; + bool isEmergency() { return emergency_; } + bool allDead(){ return isnil (running_); } public: - SubsystemRunner (Option& opts) - : opts_(opts) - , emergency_(false) - , start_(bind (&SubsystemRunner::triggerStartup, this,_1)) - , stopIt_(bind (&Subsys::triggerShutdown, _1)) + : opts_{opts} + , emergency_{false} { } void maybeRun (Subsys& susy) { - Lock guard (this); + Lock guard{this}; if (!susy.isRunning() && susy.shouldStart (opts_)) triggerStartup (&susy); } void - shutdownAll () + shutdownAll() { - Lock guard (this); - for_each (running_, stopIt_); + Lock guard{this}; + for_each (running_, [](Subsys* susy){ susy->triggerShutdown(); }); } void triggerEmergency (bool cond) - { - Lock guard (this); + { + Lock guard{this}; if (cond) emergency_= true; } bool - wait () + wait() { - Lock wait_blocking(this, &SubsystemRunner::allDead); - ////////////////////////////////////////////////////////////OOO Emergency-Exit richtig implementieren - if (isEmergencyExit()) - usleep(2*1000*1000); - return isEmergencyExit(); + Lock blocking{this, [&]{ return allDead() or isEmergency(); }}; + if (isEmergency()) + blocking.wait_for (EMERGENCY_STOP, [&]{ return allDead(); }); + // ...prevent deadlock on emergency by limiting shutdown wait + return isEmergency(); } private: - bool isEmergencyExit () { return emergency_; } - void triggerStartup (Subsys* susy) { + auto isRunning = [](Subsys* susy){ return susy->isRunning(); }; + auto triggerStart = [this](Subsys* susy){ triggerStartup(susy); }; + auto termCallback = [this,susy] + (string* problem) + { + this->sigTerm (susy, problem); + }; REQUIRE (susy); - if (susy->isRunning()) return; + if (isRunning(susy)) return; INFO (subsystem, "Triggering startup of subsystem \"%s\"", cStr(*susy)); - for_each (susy->getPrerequisites(), start_); - bool started = susy->start (opts_, [this,susy] - (string* problem) - { - this->sigTerm (susy, problem); - }); + for_each (susy->getPrerequisites(), triggerStart ); + bool started = susy->start (opts_, termCallback); if (started) { - if (susy->isRunning()) + if (isRunning(susy)) running_.push_back (susy); // now responsible for managing the started subsystem else - throw error::Logic("Subsystem "+string(*susy)+" failed to start"); + throw error::Logic(_Fmt{"Subsystem %s failed to start"} % *susy); } - if (not and_all (susy->getPrerequisites(), isRunning() )) + if (not and_all (susy->getPrerequisites(), isRunning )) { susy->triggerShutdown(); - throw error::State("Unable to start all prerequisites of Subsystem "+string(*susy)); + throw error::State(_Fmt{"Unable to start all prerequisites of Subsystem %s"} % *susy); } } void sigTerm (Subsys* susy, string* problem) ///< called from subsystem on termination { REQUIRE (susy); - Lock sync (this); + Lock sync{this}; triggerEmergency(not isnil (problem)); INFO (subsystem, "Subsystem '%s' terminated.", cStr(*susy)); WARN_IF (not isnil(problem), subsystem, "Irregular shutdown caused by: %s", cStr(*problem)); @@ -218,24 +213,8 @@ namespace lumiera { "without resetting running state", cStr(*susy)); removeall (running_, susy); shutdownAll(); - sync.notify(); + sync.notify_one(); } - - bool - allDead () - { - if (isEmergencyExit()) - { -// Lock sync(this); -// if (!sync.isTimedWait()) -// sync.setTimeout(EMERGENCYTIMEOUT); - ////////////////////////////////////////////////////////////OOO Emergency-Exit richtig implementieren - return true; - } - - return isnil (running_); // end wait if no running subsystem left - } - }; diff --git a/src/lib/allocation-cluster.cpp b/src/lib/allocation-cluster.cpp index 865c74d02..d9389c270 100644 --- a/src/lib/allocation-cluster.cpp +++ b/src/lib/allocation-cluster.cpp @@ -106,7 +106,7 @@ namespace lib { void AllocationCluster::MemoryManager::reset (TypeInfo info) { - Lock sync(this); + Lock sync{this}; if (0 < mem_.size()) purge(); type_ = info; @@ -121,7 +121,7 @@ namespace lib { void AllocationCluster::MemoryManager::purge() { - Lock sync(this); + Lock sync{this}; REQUIRE (type_.killIt, "we need a deleter function"); REQUIRE (0 < type_.allocSize, "allocation size unknown"); @@ -145,7 +145,7 @@ namespace lib { inline void* AllocationCluster::MemoryManager::allocate() { - Lock sync(this); + Lock sync{this}; REQUIRE (0 < type_.allocSize); REQUIRE (top_ <= mem_.size()); @@ -165,7 +165,7 @@ namespace lib { inline void AllocationCluster::MemoryManager::commit (void* pendingAlloc) { - Lock sync(this); + Lock sync{this}; REQUIRE (pendingAlloc); ASSERT (top_ < mem_.size()); diff --git a/src/lib/call-queue.hpp b/src/lib/call-queue.hpp index b96bc4cfc..3d046e305 100644 --- a/src/lib/call-queue.hpp +++ b/src/lib/call-queue.hpp @@ -76,7 +76,7 @@ namespace lib { throw error::Logic( "Unbound Functor fed to dispatcher CallQueue" , error::LUMIERA_ERROR_BOTTOM_VALUE); { - Lock sync(this); + Lock sync{this}; queue_.feed (move(op)); } return *this; @@ -89,7 +89,7 @@ namespace lib { { Operation operate; { - Lock sync(this); + Lock sync{this}; operate = move (*queue_); ++queue_; } @@ -105,7 +105,7 @@ namespace lib { size_t size() const { - Lock sync(this); + Lock sync{this}; return queue_.size(); } diff --git a/src/lib/symbol-table.hpp b/src/lib/symbol-table.hpp index e252ffb68..fabc26c04 100644 --- a/src/lib/symbol-table.hpp +++ b/src/lib/symbol-table.hpp @@ -80,7 +80,7 @@ namespace lib { Literal internedString (string && symbolString) { - Lock sync(this); + Lock sync{this}; auto res = table_.insert (forward (symbolString)); return res.first->c_str(); } diff --git a/src/lib/sync.hpp b/src/lib/sync.hpp index 949fa8a4c..1e3fbae8d 100644 --- a/src/lib/sync.hpp +++ b/src/lib/sync.hpp @@ -24,20 +24,28 @@ /** @file sync.hpp ** Object Monitor based synchronisation. ** The actual locking, signalling and waiting is implemented by delegating to the - ** raw pthreads locking/sync calls. Rather, the purpose of the Sync baseclass is - ** to support locking based on the object monitor pattern. This pattern - ** describes a way of dealing with synchronisation known to play well with - ** scoping, encapsulation and responsibility for a single purpose. + ** a _mutex_ and for waiting also to a _condition variable_ as provided by the C++ + ** standard library. The purpose of the Sync baseclass is to provide a clear and simple + ** „everyday“ concurrency coordination based on the object monitor pattern. This + ** pattern describes a way of dealing with synchronisation known to play well with + ** scoping, encapsulation and responsibility for a single purpose. For performance + ** critical code, other solutions (e.g. Atomics) might be preferable. + ** + ** # Usage ** ** A class becomes _lockable_ by inheriting from lib::Sync with the appropriate ** parametrisation. This causes any instance to inherit a monitor member (object), - ** managing a mutex and (optionally) a condition variable for waiting. The actual - ** synchronisation is achieved by placing a guard object as local (stack) variable - ** into a given scope (typically a member function body). This guard object of - ** class lib::Sync::Lock accesses the enclosing object's monitor and automatically - ** manages the locking and unlocking; optionally it may also be used for waiting - ** on a condition. - ** + ** maintaining a dedicated a mutex and (optionally) a condition variable for waiting. + ** The actual synchronisation is achieved by placing a guard object as local (stack) + ** variable into a given scope (typically a member function body). This guard object + ** of class lib::Sync::Lock accesses the enclosing object's monitor and automatically + ** manages the locking and unlocking; optionally it may also be used to perform a + ** [wait-on-condition] — the call to `Lock::wait(predicate)` will first check the + ** predicate, and if it does not yield `true`, the thread will be put to sleep. + ** It must be awakened from another thread by invoking `notify_one|all` and will + ** then re-check the condition predicate. The `wait_for` variant allows to set + ** a timeout to limit the sleep state, which implies however that the call may + ** possibly return `false` in case the condition predicate is not (yet) fulfilled. ** @note ** - It is important to select a suitable parametrisation of the monitor. ** This is done by specifying one of the defined policy classes. @@ -48,15 +56,11 @@ ** - The "this" pointer is fed to the ctor of the Lock guard object. Thus ** you may use any object's monitor as appropriate, especially in cases ** when adding the monitor to a given class may cause size problems. - ** - For sake of completeness, this implementation provides the ability for - ** timed waits. But please consider that in most cases there are better - ** solutions for running an operation with given timeout by utilising the - ** Lumiera scheduler. Thus use of timed waits is \b discouraged. ** - There is a special variant of the Lock guard called ClassLock, which ** can be used to lock based on a type, not an instance. - ** - in DEBUG mode, the implementation includes NoBug resource tracking. ** ** @todo WIP-WIP 10/2023 switch from POSIX to C++14 ///////////////////////////////////////////////////////TICKET #1279 : also clean-up the Object-Monitor implementation + ** [wait-on-condition]: https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait ** @see mutex.h ** @see sync-locking-test.cpp ** @see sync-waiting-test.cpp @@ -226,6 +230,12 @@ namespace lib { } }; + struct NoLocking + { + void lock() { /* boo */ } + void unlock() noexcept { /* hoo */ } + }; + @@ -345,6 +355,7 @@ namespace lib { using Monitor = sync::Monitor; mutable Monitor objectMonitor_; + public: static Monitor& getMonitor(Sync const* forThis) { @@ -353,9 +364,8 @@ namespace lib { } - public: /*****************************************//** - * scoped object to control the actual locking. + * scoped guard to control the actual locking. */ class Lock : util::NonCopyable @@ -364,45 +374,37 @@ namespace lib { public: template - Lock(X* it) : mon_(getMonitor(it)){ mon_.lock(); } + Lock(X* it) : mon_{getMonitor(it)}{ mon_.lock(); } ~Lock() { mon_.unlock(); } - void notify() { mon_.notify_one(); } - void notifyAll() { mon_.notify_all(); } + void notify_one() { mon_.notify_one(); } + void notify_all() { mon_.notify_all(); } - template - bool - wait (C&& cond, ulong timeout_ms=0) //////////////////////////////////////TICKET #1055 : accept std::chrono values here + template + void + wait (PRED&& predicate) { - if (timeout_ms) - return mon_.wait_for (std::chrono::milliseconds(timeout_ms) - ,std::forward (cond)); - else - { - mon_.wait (std::forward (cond)); - return true; - } + mon_.wait (std::forward(predicate)); } - template + template bool - wait (X& instance, bool (X::*predicate)(void), ulong timeout_ms=0) //////////////////////TICKET #1051 : enable use of lambdas + wait_for (DUR const& timeout, PRED&& predicate) { - return wait([&]{ return (instance.*predicate)(); }, timeout_ms); + return mon_.wait_for (timeout, std::forward (predicate)); } /** convenience shortcut: - * Locks and immediately enters wait state, - * observing a condition defined as member function. - * @deprecated WARNING this function is not correct! ////////////////////////////TICKET #1051 - * Lock is not released on error from within wait() /////TODO is actually fixed now; retain this API?? + * Locks and immediately enters wait state on the given predicate */ - template - Lock(X* it, bool (X::*method)(void)) + template + Lock(X* it, PRED&& predicate) : mon_(getMonitor(it)) { mon_.lock(); - try { wait(*it,method); } + try { + mon_.wait (std::forward(predicate)); + } catch(...) { mon_.unlock(); @@ -413,7 +415,7 @@ namespace lib { protected: /** for creating a ClassLock */ Lock(Monitor& m) - : mon_(m) + : mon_{m} { mon_.lock(); } diff --git a/src/lib/typed-counter.hpp b/src/lib/typed-counter.hpp index 90b39cd12..f32589dcd 100644 --- a/src/lib/typed-counter.hpp +++ b/src/lib/typed-counter.hpp @@ -157,7 +157,7 @@ namespace lib { IxID typeID = TypedContext::ID::get(); if (size() < typeID) { // protect against concurrent slot allocations - Lock sync(this); + Lock sync{this}; if (size() < typeID) counters_.resize (typeID); } diff --git a/src/steam/assetmanager.cpp b/src/steam/assetmanager.cpp index 98914b588..1f3838c5c 100644 --- a/src/steam/assetmanager.cpp +++ b/src/steam/assetmanager.cpp @@ -122,7 +122,7 @@ namespace asset { //////////////////////////////////////////////////////////TICKET #840 check validity of Ident Category ID asset_id (getID (idi)); - DB::Lock guard(®istry); + DB::Lock guard{®istry}; //////////////////////////////////////////////////////////TICKET #840 handle duplicate Registrations lib::P smart_ptr (obj, &destroy); diff --git a/src/steam/control/command-registry.hpp b/src/steam/control/command-registry.hpp index ea32365fd..938328180 100644 --- a/src/steam/control/command-registry.hpp +++ b/src/steam/control/command-registry.hpp @@ -151,7 +151,7 @@ namespace control { void track (Symbol cmdID, Command const& commandHandle) { - Lock sync(this); + Lock sync{this}; REQUIRE (commandHandle); if (contains (index_,cmdID) || contains(ridx_, &commandHandle)) @@ -173,7 +173,7 @@ namespace control { bool remove (Symbol cmdID) { - Lock sync(this); + Lock sync{this}; bool actually_remove = contains (index_,cmdID); if (actually_remove) @@ -197,7 +197,7 @@ namespace control { Command queryIndex (Symbol cmdID) { - Lock sync(this); + Lock sync{this}; return getValue_or_default (index_, cmdID, Command() ); } //if not found @@ -209,7 +209,7 @@ namespace control { Symbol findDefinition (Command const& cmdInstance) const { - Lock sync(this); + Lock sync{this}; return getValue_or_default (ridx_, &cmdInstance, Symbol::BOTTOM ); } //used as Key diff --git a/src/steam/control/looper.hpp b/src/steam/control/looper.hpp index bee2e80bc..b0117b333 100644 --- a/src/steam/control/looper.hpp +++ b/src/steam/control/looper.hpp @@ -53,6 +53,7 @@ #include "vault/real-clock.hpp" #include +#include @@ -63,6 +64,7 @@ namespace control { using lib::time::TimeVar; using lib::time::Offset; using lib::time::Duration; + using std::chrono::milliseconds; using vault::RealClock; namespace { @@ -206,14 +208,15 @@ namespace control { return not isDying(); } - ulong /////////////////////////////////////////////TICKET #1056 : better return a std::chrono value here + milliseconds getTimeout() const { if (not useTimeout()) - return 0; + return milliseconds::zero(); else - return wakeTimeout_ms() - * (isDirty_ and not isWorking()? 1 : slowdownFactor()); + return milliseconds{ + wakeTimeout_ms() + * (isDirty_ and not isWorking()? 1 : slowdownFactor())}; } diff --git a/src/steam/control/steam-dispatcher.cpp b/src/steam/control/steam-dispatcher.cpp index bc6e30dc7..a97fbc223 100644 --- a/src/steam/control/steam-dispatcher.cpp +++ b/src/steam/control/steam-dispatcher.cpp @@ -163,7 +163,7 @@ namespace control { init_.sync(); // done with setup; loop may run now.... INFO (session, "Steam-Dispatcher running..."); { - Lock sync(this); // open public session interface: + Lock sync{this}; // open public session interface: commandService_.createInstance(*this); } } @@ -182,42 +182,41 @@ namespace control { void activateCommandProecssing() { - Lock sync(this); + Lock sync{this}; looper_.enableProcessing(true); INFO (command, "Session command processing activated."); - sync.notifyAll(); + sync.notify_all(); } void deactivateCommandProecssing() { - Lock sync(this); + Lock sync{this}; looper_.enableProcessing(false); INFO (command, "Session command interface closed."); - sync.notifyAll(); + sync.notify_all(); } void requestStop() noexcept { - Lock sync(this); + Lock sync{this}; commandService_.shutdown(); // closes Session interface looper_.triggerShutdown(); - sync.notifyAll(); + sync.notify_all(); } void awaitStateProcessed() const { - Lock(unConst(this)).wait(unConst(*this), &DispatcherLoop::isStateSynched); ///////////////////////TICKET #1051 : support bool-λ and fix the correctness-error in the »convenience shortcut« - ////////////////////////TICKET #1057 : const correctness on wait predicate - // wake-up typically by updateState() + Lock{this, [&]{ return isStateSynched(); }}; + // wake-up typically by updateState() } size_t size() const { - Lock sync(this); + Lock sync{this}; return queue_.size(); } @@ -227,17 +226,17 @@ namespace control { void enqueue (Command&& cmd) override { - Lock sync(this); + Lock sync{this}; queue_.feed (move (cmd)); - sync.notifyAll(); + sync.notify_all(); } void clear() override { - Lock sync(this); + Lock sync{this}; queue_.clear(); - sync.notifyAll(); + sync.notify_all(); } private: @@ -282,8 +281,8 @@ namespace control { void awaitAction() ///< at begin of loop body... { - Lock(this).wait(looper_, &Looper::requireAction, - looper_.getTimeout()); + Lock{this}.wait_for (looper_.getTimeout() + ,[&]{ return looper_.requireAction(); }); } void @@ -291,11 +290,11 @@ namespace control { { looper_.markStateProcessed(); if (looper_.isDisabled()) // otherwise wake-up would not be safe - Lock(this).notifyAll(); + getMonitor(this).notify_all(); } bool - isStateSynched() + isStateSynched() const { if (thread_.invokedWithinThread()) throw error::Fatal("Possible Deadlock. " @@ -310,7 +309,7 @@ namespace control { { Command cmd; { - Lock sync(this); + Lock sync{this}; if (not queue_.empty()) cmd = queue_.pop(); } @@ -361,7 +360,7 @@ namespace control { bool SteamDispatcher::start (Subsys::SigTerm termNotification) { - Lock sync(this); + Lock sync{this}; if (runningLoop_) return false; runningLoop_ = @@ -389,7 +388,7 @@ namespace control { void SteamDispatcher::endRunningLoopState() { - Lock sync(this); + Lock sync{this}; if (runningLoop_) runningLoop_.reset(); // delete DispatcherLoop object else @@ -407,7 +406,7 @@ namespace control { bool SteamDispatcher::isRunning() { - Lock sync(this); + Lock sync{this}; return bool(runningLoop_); } @@ -419,7 +418,7 @@ namespace control { SteamDispatcher::requestStop() noexcept { try { - Lock sync(this); + Lock sync{this}; if (runningLoop_) runningLoop_->requestStop(); } @@ -439,7 +438,7 @@ namespace control { void SteamDispatcher::activate() { - Lock sync(this); + Lock sync{this}; active_ = true; if (runningLoop_) runningLoop_->activateCommandProecssing(); @@ -455,7 +454,7 @@ namespace control { void SteamDispatcher::deactivate() { - Lock sync(this); + Lock sync{this}; active_ = false; if (runningLoop_) runningLoop_->deactivateCommandProecssing(); @@ -471,7 +470,7 @@ namespace control { void SteamDispatcher::awaitDeactivation() { - Lock sync(this); + Lock sync{this}; if (runningLoop_) runningLoop_->awaitStateProcessed(); } @@ -481,7 +480,7 @@ namespace control { void SteamDispatcher::clear() { - Lock sync(this); + Lock sync{this}; if (not empty()) { WARN (command, "DISCARDING pending Session commands."); @@ -494,7 +493,7 @@ namespace control { bool SteamDispatcher::empty() const { - Lock sync(this); + Lock sync{this}; return not runningLoop_ or 0 == runningLoop_->size(); } diff --git a/src/steam/mobject/session/sess-manager-impl.cpp b/src/steam/mobject/session/sess-manager-impl.cpp index a4a183ec0..241eb163e 100644 --- a/src/steam/mobject/session/sess-manager-impl.cpp +++ b/src/steam/mobject/session/sess-manager-impl.cpp @@ -228,7 +228,7 @@ namespace session { bool SessManagerImpl::isUp() { - Lock sync(this); + Lock sync{this}; return bool(pSess_); ///////////////////// TICKET #702 possible race, because this gets true way before the interface is up } @@ -238,7 +238,7 @@ namespace session { void SessManagerImpl::clear() { - Lock sync(this); + Lock sync{this}; pSess_->clear(); } @@ -251,7 +251,7 @@ namespace session { void SessManagerImpl::close() { - Lock sync(this); + Lock sync{this}; if (isUp()) lifecycle_->shutDown(); pSess_.reset(); @@ -266,7 +266,7 @@ namespace session { void SessManagerImpl::reset() { - Lock sync(this); + Lock sync{this}; if (isUp()) lifecycle_->shutDown(); lifecycle_->pullUp(); @@ -279,7 +279,7 @@ namespace session { SessManagerImpl::load() { UNIMPLEMENTED ("load serialised session"); - Lock sync(this); + Lock sync{this}; if (isUp()) lifecycle_->shutDown(); lifecycle_->pullUp(); diff --git a/src/steam/play/output-director.cpp b/src/steam/play/output-director.cpp index 6d1b1acec..b8039fb88 100644 --- a/src/steam/play/output-director.cpp +++ b/src/steam/play/output-director.cpp @@ -71,7 +71,7 @@ namespace play { bool OutputDirector::connectUp() { - Lock sync(this); + Lock sync{this}; REQUIRE (not shutdown_initiated_); player_.createInstance(); @@ -122,7 +122,7 @@ namespace play { void OutputDirector::bringDown (SigTerm completedSignal) { - Lock sync(this); + Lock sync{this}; string problemLog; if (not isOperational()) { diff --git a/src/steam/play/play-service.cpp b/src/steam/play/play-service.cpp index 69dea1da9..c924fc922 100644 --- a/src/steam/play/play-service.cpp +++ b/src/steam/play/play-service.cpp @@ -113,7 +113,7 @@ namespace play { throw; } - Lock sync(this); + Lock sync{this}; processes_.push_back (frontend); // keeping a weak-reference return frontend; } @@ -132,7 +132,7 @@ namespace play { /////////////////////////////////////////////TICKET #867 : somehow ensure sane abort of all attached calculation efforts delete dyingProcess; - Lock sync(this); + Lock sync{this}; remove_if (processes_, isDead); } diff --git a/src/vault/enginefacade.cpp b/src/vault/enginefacade.cpp index ad358900c..bd6f8e713 100644 --- a/src/vault/enginefacade.cpp +++ b/src/vault/enginefacade.cpp @@ -72,7 +72,7 @@ namespace vault { bool checkRunningState () noexcept override { - //Lock guard (*this); + //Lock guard{*this}; TODO ("implement detecting running state"); return false; } diff --git a/src/vault/netnodefacade.cpp b/src/vault/netnodefacade.cpp index af347d532..75c28cb41 100644 --- a/src/vault/netnodefacade.cpp +++ b/src/vault/netnodefacade.cpp @@ -67,7 +67,7 @@ namespace vault { bool checkRunningState () noexcept override { - //Lock guard (*this); + //Lock guard{*this}; TODO ("implement detecting running state"); return false; } diff --git a/src/vault/scriptrunnerfacade.cpp b/src/vault/scriptrunnerfacade.cpp index c04dd852a..80ec4b05f 100644 --- a/src/vault/scriptrunnerfacade.cpp +++ b/src/vault/scriptrunnerfacade.cpp @@ -69,7 +69,7 @@ namespace vault { bool checkRunningState () noexcept override { - //Lock guard (*this); + //Lock guard{*this}; TODO ("implement detecting running state"); return false; } diff --git a/tests/basics/call-queue-test.cpp b/tests/basics/call-queue-test.cpp index 418bcbe00..f30b16f55 100644 --- a/tests/basics/call-queue-test.cpp +++ b/tests/basics/call-queue-test.cpp @@ -191,7 +191,7 @@ namespace test{ void countConsumerCall (uint increment) { - Lock sync(this); // NOTE: will be invoked from some random other thread + Lock sync{this}; // NOTE: will be invoked from some random other thread consumerSum += increment; } diff --git a/tests/core/steam/control/dispatcher-looper-test.cpp b/tests/core/steam/control/dispatcher-looper-test.cpp index 3f327ae7f..d18223eac 100644 --- a/tests/core/steam/control/dispatcher-looper-test.cpp +++ b/tests/core/steam/control/dispatcher-looper-test.cpp @@ -38,6 +38,8 @@ namespace test { namespace { // test fixture... + using Dur = std::chrono::duration; + /** * @todo this value should be retrieved from configuration ////////////////////////////////TICKET #1052 : access application configuration * @see Looper::establishWakeTimeout() @@ -45,22 +47,22 @@ namespace test { const uint EXPECTED_BUILDER_DELAY_ms = 50; bool - isFast (uint timeoutDelay_ms) + isFast (milliseconds timeoutDelay) { - return timeoutDelay_ms < 1.2 * EXPECTED_BUILDER_DELAY_ms - and 0 < timeoutDelay_ms; + return timeoutDelay < Dur{1.2 * EXPECTED_BUILDER_DELAY_ms} + and 0ms < timeoutDelay; } bool - isSlow (uint timeoutDelay_ms) + isSlow (milliseconds timeoutDelay) { - return timeoutDelay_ms >= 1.2 * EXPECTED_BUILDER_DELAY_ms; + return timeoutDelay >= Dur{1.2 * EXPECTED_BUILDER_DELAY_ms}; } bool - isDisabled (uint timeoutDelay_ms) + isDisabled (milliseconds timeoutDelay) { - return 0 == timeoutDelay_ms; + return 0ms == timeoutDelay; } @@ -129,9 +131,9 @@ namespace test { setup.has_commands_in_queue = true; CHECK (looper.requireAction()); - uint timeout = looper.getTimeout(); - CHECK (10 < timeout, "configured idle timeout %2u to short", timeout); - CHECK (timeout < 800, "configured idle timeout %3u to long", timeout); + milliseconds timeout = looper.getTimeout(); + CHECK (10ms < timeout, "configured idle timeout %2l to short", timeout.count()); + CHECK (timeout < 800ms, "configured idle timeout %3l to long", timeout.count()); } diff --git a/tests/library/sync-barrier-performance-test.cpp b/tests/library/sync-barrier-performance-test.cpp index 4ac087218..1d3bab1af 100644 --- a/tests/library/sync-barrier-performance-test.cpp +++ b/tests/library/sync-barrier-performance-test.cpp @@ -74,10 +74,10 @@ namespace test { void sync() { - Lock sync(this); + Lock sync{this}; --latch_; - sync.wait(*this, &MonitorSync::allPassed); - sync.notifyAll(); + sync.wait ([this]{ return allPassed(); }); + sync.notify_all(); } private: diff --git a/tests/library/sync-classlock-test.cpp b/tests/library/sync-classlock-test.cpp index 4cbbcf426..b835ec2b3 100644 --- a/tests/library/sync-classlock-test.cpp +++ b/tests/library/sync-classlock-test.cpp @@ -87,7 +87,7 @@ namespace test { }; for (auto& thread : threads) - thread.join(); // block until thread terminates + thread.join(); // block until thread terminates CHECK (contended == NUM_THREADS * NUM_LOOP, "ALARM: Lock failed, concurrent modification " diff --git a/tests/library/sync-locking-test.cpp b/tests/library/sync-locking-test.cpp index ffb7d3bb2..65735e8e1 100644 --- a/tests/library/sync-locking-test.cpp +++ b/tests/library/sync-locking-test.cpp @@ -62,7 +62,7 @@ namespace test{ void pause () { - Lock guard (this); // note recursive lock + Lock guard{this}; // note recursive lock for ( uint i=0, lim=(rand() % MAX_PAUSE); i - -using std::bind; using test::Test; +using lib::explore; +using std::this_thread::yield; +using std::this_thread::sleep_for; +using std::chrono_literals::operator ""us; namespace lib { - namespace test { - +namespace test{ + namespace { // private test classes and data... - const uint NUM_THREADS = 20; + const uint NUM_THREADS = 200; const uint MAX_RAND_SUMMAND = 100; + + /** Helper to verify a contended chain calculation */ + template class Checker - : public lib::Sync<> + : public Sync { - volatile ulong hot_sum_; - ulong control_sum_; + size_t hot_sum_{0}; + size_t control_sum_{0}; + + using Lock = typename Sync::Lock; public: - Checker() : hot_sum_(0), control_sum_(0) { } - bool verify() ///< verify test values got handled and accounted { + Lock guard{this}; return 0 < hot_sum_ - && control_sum_ == hot_sum_; + and control_sum_ == hot_sum_; } uint createVal() ///< generating test values, remembering the control sum { - uint val(rand() % MAX_RAND_SUMMAND); + uint val{rand() % MAX_RAND_SUMMAND}; control_sum_ += val; return val; } @@ -73,55 +79,31 @@ namespace lib { void addValues (uint a, uint b) ///< to be called concurrently { - Lock guard(this); + Lock guard{this}; hot_sum_ *= 2; - usleep (200); // force preemption + sleep_for (200us); // force preemption hot_sum_ += 2 * (a+b); - usleep (200); + sleep_for (200us); hot_sum_ /= 2; } }; - - - Checker checksum; ///< global variable used by multiple threads - - - - - struct TestThread - : Thread - { - TestThread() - : Thread{&TestThread::theOperation, checksum.createVal(), checksum.createVal()} - { } // note the binding (functor object) is passed as anonymous temporary - - - void - theOperation (uint a, uint b) ///< the actual operation running in a separate thread - { - checksum.addValues (a,b); - } - }; - - } // (End) test classes and data.... + }// (End) test classes and data.... - - - - - /**********************************************************************//** - * @test use the Lumiera Vault to create some new threads, utilising the - * lumiera::Thread wrapper for binding to an arbitrary operation - * and passing the appropriate context. - * - * @see vault::Thread - * @see threads.h + /******************************************************************//** + * @test verify the object monitor provides locking and to prevent + * data corruption on concurrent modification of shared storage. + * - use a chained calculation with deliberate sleep state + * while holding onto an intermediary result + * - run this calculation contended by a huge number of threads + * - either use locking or no locking + * @see sync.happ + * @see thread.hpp */ class SyncLocking_test2 : public Test { @@ -129,11 +111,31 @@ namespace lib { virtual void run (Arg) { - TestThread instances[NUM_THREADS] SIDEEFFECT; + CHECK (can_calc_without_Error()); + CHECK (can_calc_without_Error()); + CHECK (not can_calc_without_Error()); + } + + + template + bool + can_calc_without_Error() + { + Checker checksum; // shared variable used by multiple threads - usleep (200000); // pause 200ms for the threads to terminate..... + lib::ScopedCollection threads{NUM_THREADS}; + for (uint i=1; i<=NUM_THREADS; ++i) + threads.emplace ([&checksum, // Note: added values prepared in main thread + a = checksum.createVal(), + b = checksum.createVal()] + { + checksum.addValues (a,b); + }); - CHECK (checksum.verify()); + while (explore(threads).has_any()) + yield(); // wait for all threads to terminate + + return checksum.verify(); } }; diff --git a/tests/library/sync-timedwait-test.cpp b/tests/library/sync-timedwait-test.cpp index 32e912a7a..855cde75d 100644 --- a/tests/library/sync-timedwait-test.cpp +++ b/tests/library/sync-timedwait-test.cpp @@ -33,6 +33,7 @@ using test::Test; using std::chrono::system_clock; +using std::chrono::milliseconds; namespace lib { @@ -40,7 +41,8 @@ namespace test{ namespace { // test parameters... - const uint WAIT_mSec = 20; ///< milliseconds to wait before timeout + const uint WAIT_mSec = 20; ///< milliseconds to wait before timeout + const milliseconds TIMEOUT{WAIT_mSec}; using CLOCK_SCALE = std::milli; // Results are in ms using Dur = std::chrono::duration; @@ -71,12 +73,12 @@ namespace test{ virtual void run (Arg) { - Lock lock(this); + Lock lock{this}; auto start = system_clock::now(); auto salvation = []{ return false; }; - bool fulfilled = lock.wait (salvation, WAIT_mSec); + bool fulfilled = lock.wait_for (TIMEOUT, salvation); CHECK (not fulfilled); // condition not fulfilled, but timeout diff --git a/tests/library/sync-waiting-test.cpp b/tests/library/sync-waiting-test.cpp index ba5276e2b..c8323408e 100644 --- a/tests/library/sync-waiting-test.cpp +++ b/tests/library/sync-waiting-test.cpp @@ -31,89 +31,46 @@ #include "lib/thread.hpp" #include "lib/sync.hpp" -#include #include using test::Test; +using std::atomic_uint; using std::atomic_bool; +using std::this_thread::sleep_for; +using std::chrono_literals::operator ""ms; namespace lib { namespace test{ - namespace { // private test classes and data... - - - /** Interface defining the basic interaction pattern for this test */ - class Token - { - public: - - /** blocking concurrent operation */ - virtual void getIt() =0; - - /** start the notification chain */ - virtual void provide (uint val) =0; - - /** harvesting the result...*/ - uint result () { return sum_; } - - - protected: - volatile uint sum_, input_; - - virtual ~Token() {} - - Token() : sum_(0), input_(0) {} - }; - + namespace { // test subject... /** demonstrates how to wait on a simple boolean flag */ class SyncOnBool - : public Token, - public Sync + : public Sync { - protected: + atomic_uint sum_{0}, input_{0}; atomic_bool got_new_data_{false}; public: void getIt() { - Lock(this).wait ([this]{ return bool(got_new_data_); }); + Lock await{this, [&]{ return bool(got_new_data_); }}; sum_ += input_; } void provide (uint val) { - Lock sync(this); + Lock sync{this}; input_ = val; got_new_data_ = true; - sync.notifyAll(); - } - }; - - - /** this variant demonstrates how to wait on an condition - * defined in terms of a (bool) member function - */ - class SyncOnMemberPredicate - : public SyncOnBool - { - bool checkTheFlag() { return this->got_new_data_; } - - public: - void getIt() - { - Lock await(this, &SyncOnMemberPredicate::checkTheFlag); /////////////////////TODO becomes obsolete with the API change - sum_ += input_; + sync.notify_all(); } + /** harvesting the result...*/ + uint result () { return sum_; } }; - - } // (End) test classes and data.... - - - + }//(End) test subject. @@ -124,11 +81,9 @@ namespace test{ /************************************************************************//** * @test concurrent waiting and notification, implemented via object monitor. * This test covers the second part of the monitor pattern, which builds upon - * the locking part, additionally using an embedded condition. We provide - * several pre-configured ways of specifying the condition to wait upon. - * - check a boolean flag - * - evaluate a member function as predicate - * + * the locking part, additionally using an embedded condition. Two interwoven + * threads are created, both blocked until a start value is given. Once awakened, + * each thread should add the start value to a common sum field. * @see SyncLocking_test * @see sync.hpp */ @@ -138,43 +93,26 @@ namespace test{ virtual void run (Arg) { - SyncOnBool use_sync_var; - waitPingPong (use_sync_var); + SyncOnBool token; - SyncOnMemberPredicate use_member_pred; - waitPingPong (use_member_pred); - } - - - /** - * Helper actually performing the test: - * creates two threads and let them block and wait until a start value is given. - * When awakened, each thread should add the start value to a common sum field. - * @param tok object containing the monitor and condition to be tested. - */ - void - waitPingPong (Token& tok) - { - typedef ThreadJoinable<> Thread; //////////////////////////////////////WIP - - Thread ping ("SyncWaiting ping", [&]{ return tok.getIt(); }); - Thread pong ("SyncWaiting pong", [&]{ return tok.getIt(); }); + ThreadJoinable ping ("SyncWaiting ping", [&]{ token.getIt(); }); + ThreadJoinable pong ("SyncWaiting pong", [&]{ token.getIt(); }); CHECK (ping); CHECK (pong); - CHECK (0 == tok.result()); + CHECK (0 == token.result()); - usleep (100000); // if the threads don't block correctly, they've missed their chance by now... + sleep_for (100ms); // if the threads don't block correctly, they've missed their chance by now... // kick off the notification cascade... uint val = (rand() % 1000); - tok.provide (val); + token.provide (val); // wait for the two Threads to finish their handshake pong.join(); ping.join(); - CHECK (2*val == tok.result()); + CHECK (2*val == token.result()); } }; diff --git a/tests/stage/bus-term-test.cpp b/tests/stage/bus-term-test.cpp index 038833f23..1b0d17f47 100644 --- a/tests/stage/bus-term-test.cpp +++ b/tests/stage/bus-term-test.cpp @@ -576,7 +576,7 @@ namespace test { void scheduleBorg (uint id) { - Lock sync(this); + Lock sync{this}; borgChecksum_ += id; sessionBorgs_.push(id); } @@ -584,7 +584,7 @@ namespace test { auto dispatchBorgs() { - Lock sync(this); + Lock sync{this}; return dischargeToSnapshot (sessionBorgs_); } diff --git a/tests/vault/gear/work-force-test.cpp b/tests/vault/gear/work-force-test.cpp index b4bc89db6..aa6f6231f 100644 --- a/tests/vault/gear/work-force-test.cpp +++ b/tests/vault/gear/work-force-test.cpp @@ -357,13 +357,13 @@ namespace test { void mark (std::thread::id const& tID) { - Lock guard(this); + Lock guard{this}; this->insert(tID); } operator size_t() const { - Lock guard(this); + Lock guard{this}; return this->size(); } } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 4f2d78bb8..1d5dad9aa 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -66356,7 +66356,7 @@ - + @@ -66365,7 +66365,7 @@ - + @@ -66548,8 +66548,12 @@ - - + + + + + + @@ -66579,7 +66583,9 @@ - + + + @@ -66595,7 +66601,8 @@ - + + @@ -66616,13 +66623,16 @@ - + + - - - + + + + + @@ -66635,8 +66645,8 @@ - - + + @@ -66652,9 +66662,9 @@ - - - + + + @@ -66680,6 +66690,7 @@ + @@ -66696,8 +66707,8 @@ - - + + @@ -66716,15 +66727,15 @@ - - + - + + @@ -66732,7 +66743,7 @@ - + @@ -66750,8 +66761,7 @@ Hier gibt es zwar eine Lücke, nämlich die const& auf einen rvalue — aber das hier ist keine general-purpose-Library!

- - +
@@ -66769,8 +66779,7 @@ also entweder ganz oben oder ganz unten, und ansonsten nur noch einen Template-Parameter durchgeben

- - +
@@ -66789,8 +66798,7 @@ man muß nicht speziell auf den Typ des Trägers abstellen; vielmehr kann man dann einen static-cast nachschalten, und der scheitert dann halt gegebenfalls beim Bauen. Das habe ich grade eben genauso beim neuen Thread-Wrapper gemacht, und es hat sich bisher gut bewährt. Spezielle static-asserts sind dann gar nicht notwendig

- - +
@@ -66809,8 +66817,7 @@ man hätte dann also zwei verschiedene APIs für essentiell das Gleiche

- - +
@@ -66824,7 +66831,7 @@ - + @@ -66846,25 +66853,39 @@ λ-Notation ist inzwischen allgemein bekannt, und hat den Vorteil, daß das Binding nahe am verwendeten Code liegt. Das gilt ganz besonders auch für bool-Flags, und zudem müssen wir uns dann nicht mehr mit Konvertierungen, volatile und Atomics herumschlagen

- - +
+ + + + + + +

+ Fazit: Nein — denn λ sind bereits der bessere „Support“ +

+ +
+ +
- - + + + - + - + + @@ -66875,16 +66896,20 @@ + + + - - + + - - + + + @@ -66896,14 +66921,13 @@ Im Besonderen #994 kann nun wirklich langsam zugemacht werden! Das schiebe ich nun schon so lange ¾ fertig vor mir her ... und es ist klar daß ich den letzten Schritt (TYPES<TY....>) noch länger vor mir her schiebe, wiewohl das eigentliche Problem effetiv bereits seit 2017 gelöst ist

- -
+
- - - + + + @@ -66913,15 +66937,14 @@ hier hatte ich einen »convenience-shortcut« — und der ist broken

- -
- + +
- - - - + + + + @@ -66932,10 +66955,9 @@ hab dummerweise den this-Ptr nicht mehr an dem Punkt; und das will ich auch nicht ändern — sonst hab ich in jeder dieser delegierenden Funktionen das getMonitor(this), und es ist auch inhaltlich nicht besonders präzise, schließlich arbeitet der Guard auf dem Monitor (siehe ClassLock)

- -
- + +
@@ -66945,23 +66967,22 @@ -<html> - <head> + + - </head> - <body> - <p> - ...und zwar wegen der Typedef &quot;Monitor&quot;, die nur im Scope von Sync&lt;CONF&gt; - definiert ist &#8212; das geht gut, SOLANGE niemand das Layout der Klasse Sync - &#228;ndert, niemand &#8222;aus Versehen&#8220; eine VTable einf&#252;hrt, und solange niemand + + +

+ ...und zwar wegen der Typedef "Monitor", die nur im Scope von Sync<CONF> + definiert ist — das geht gut, SOLANGE niemand das Layout der Klasse Sync + ändert, niemand „aus Versehen“ eine VTable einführt, und solange niemand den Typ Monitor abgreift, woanders instantiiert und dann diese Referenz - &#252;ber eine von Lock abgeleitete Klasse in die Referenz einspielt. <b><font color="#ea1b82">TJA</font></b>&#160; - &#9785; diesen &#8222;Jemand&#8220; gibt es bereits: SyncClasslock <font color="#ad0000">&#55357;&#56817;</font> - </p> - </body> -</html> - + über eine von Lock abgeleitete Klasse in die Referenz einspielt. TJA  + ☹ diesen „Jemand“ gibt es bereits: SyncClasslock �� +

+ +
@@ -66982,10 +67003,22 @@ und zwar, weil eine solche anonyme Instanz den umschließenden Scope nicht schützt; sie sieht aber syntaktisch genauso aus wie ein wirksamer Scope-Guard

- - +
+ + + + + + +

+ ...vielmehr wird das Problem tatsächlich gefixt, durch einen try-catch-Block; die spezielle Konstruktor-Variante bleibt damit erhalten, aber wir bieten generell keinen Support mehr für Member-Funktionen, da dies nur für den Konstruktor möglich wäre, aber nich für die frei stehende wait()-Variante. Ohnehin werden nun Lambdas bevorzugt, weil sie meist am Ort der Verwendung definiert werden und damit besser selbsterklärend sind... +

+ +
+ +
@@ -66996,16 +67029,18 @@
- - + + - + + - + + @@ -67016,12 +67051,32 @@ ...rein konzeptionell ist es nämlich nicht notwendig, das Lock zu erlangen; aber unser bisheriges API hat dazu gezwungen. Nicht schlimm, aber auch nicht schön

- -
+
- + + + + + + + + +

+ muß dazu getMonitor() public machen +

+ +
+ +
+
+ + + + + +
@@ -67257,8 +67312,8 @@
- - + + @@ -67267,7 +67322,7 @@ - + @@ -67276,9 +67331,10 @@

+
- - + +
@@ -82134,9 +82190,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -82150,7 +82206,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- Dieser Test ist zwar pfiffig geschrieben, aber er testet eigentlich den Sync/Monitor, nicht den Thread-Wrapper. In der aktuellen Form ist er sogar nahe an der Thema-Verfehlung, weil überhaupt nicht getestet wird,  daß mehrere Threads gestartet wurden und gelaufen sind + Dieser Test ist zwar pfiffig geschrieben, aber er testet eigentlich den Sync/Monitor, nicht den Thread-Wrapper. In der aktuellen Form ist er sogar nahe an der Thema-Verfehlung, weil überhaupt nicht getestet wird, daß mehrere Threads gestartet wurden und gelaufen sind

@@ -82624,24 +82680,33 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + - - - - + - - + + + + + + + + + + + + + + @@ -83047,10 +83112,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + - @@ -83644,32 +83707,93 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + + + + + + + +

+ Es handelt sich um ein eigenständiges Feature, nämlich daß ein wait abgebrochen wird +

+
    +
  • + wir haben dafür wenige eche use-Cases in der Codebasis (Emergency-Shutdown und Builder-Timeout im Steam-Dispatcher) +
  • +
  • + für feste Verzögerungen gibt es inzwischen std::this_thread::sleep_for +
  • +
  • + Angleichen an das C++ API ⟹ eigenständige Funktion wait_for() +
  • +
  • + Rückbau der dynamischen Änderbarkeit per setTimeout() +
  • +
+ +
+ +
- - - - - + + + - + + + + + + + + + + + - - - + + - - - - + + + + + + + + + + +

+ ...weil es im Konstruktor steht, würde ein Fehler hier den Konstruktor als gescheitert klassifizieren; dadurch würden zwar die Teil-Objekte abgebaut, aber der Objekt-Destruktor nicht aufgerufen. Daher +

+
    +
  • + das lock() steht ungeschützt ⟹ wenn es scheitert muß auch kein unlock() folgen +
  • +
  • + dagegen das wait() wird geschützt ⟹ bei Exception explizit unlock() und re-throw +
  • +
+ + +
+
+
+
+ + + + @@ -83680,18 +83804,34 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + - - + + - - + + + + + + + + + + + + + + + + + +