diff --git a/doc/design/architecture/Subsystems.txt b/doc/design/architecture/Subsystems.txt index 9c1cf1747..f57d2d85b 100644 --- a/doc/design/architecture/Subsystems.txt +++ b/doc/design/architecture/Subsystems.txt @@ -46,7 +46,7 @@ of the service interface. Dependency:: A relation at implementation level and thus a local property of an individual component. A dependency is something we need in order to complete the task at hand, yet a dependency lies beyond that task and - relates to concerns outside the scope and theme of this actual task. Wich means, a dependency is not + relates to concerns outside the scope and theme of this actual task. Which means, a dependency is not introduced by the task or part of the task, rather the task is the reason why some entity dealing with it needs to _pull_ the dependency, in order to be able to handle the task. So essentially, dependencies are accessed on-demand. Dependencies might be other components or services, and typically the user @@ -146,6 +146,28 @@ Any valuable work done by the user should be accepted and recorded persistently session are logged, like in a database. The user may still save snapshots, but basically any actual change is immediately recorded persistently. And thus we may crash without remorse. +Static initialisation and shutdown +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A lot of fine points can be made about when precisely static objects in C\++ will be initialised or destroyed. +However, anything beyond the scope of `main()` is not meant to be used for regular application code. Extended +initialisation, dependency management and decommissioning -- when actually necessary -- should be part of the +application code proper.footnote:[this is established ``best practice'' for good reasons. The interplay of +static lifespan, various translation units and even dynamically loaded libraries together with shared access +becomes intricate and insidious quite easily. And since in theory any static function could use some static +variable residing in another translation unit, it is always possible to construct a situation where objects +are accessed after being destroyed. Typically such objects do not even look especially ``dead'', since the +static storage remains in place and still holds possibly sane values. Static (global) variables, like raw +pointers, allow to subvert the deterministic automatic memory management, which otherwise is one of the +greatest strengths of C++. Whenever we find ourselves developing extended collaborative logic based on +several statics, we should consider to transform this logic into regular objects, which are easier to +test and better to reason about. If it really can not be avoided to use such units of logic from a +static context, it should at least be packaged as a single object, plus we should ensure this logic +can only be accessed through a regular (non static) object as front-end. Packaged this way, the +most common and dangerous pitfalls with statics can be avoided.] And since Lumiera indeed allows +for link:{ldoc}/technical/library/Dependencies.html[[lazily initialised dependencies], we +establish the policy that *destructors must not rely on dependencies*. In fact, they should +not do any tangible work at all, beyond releasing other resources. + Lifecycle Events ~~~~~~~~~~~~~~~~ The Application as a whole conducts a well defined lifecycle; whenever transitioning to the next phase, diff --git a/src/lib/depend.hpp b/src/lib/depend.hpp index ece8c3d00..3f4ccd85e 100644 --- a/src/lib/depend.hpp +++ b/src/lib/depend.hpp @@ -125,14 +125,25 @@ namespace lib { Creator creator_; Deleter deleter_; + bool deceased_ =false; public: DependencyFactory() = default; ~DependencyFactory() { + deceased_ = true; if (deleter_) deleter_(); } + void + zombieCheck() + { + if (deceased_) + throw error::Fatal("DependencyFactory invoked out of order during Application shutdown. " + "Lumiera Policy violated: Dependencies must not be used from destructors." + ,error::LUMIERA_ERROR_LIFECYCLE); + } + OBJ* operator() () { @@ -295,6 +306,7 @@ namespace lib { SRV* object = instance.load (std::memory_order_acquire); if (!object) { + factory.zombieCheck(); Lock guard; object = instance.load (std::memory_order_relaxed); diff --git a/src/lib/dependable-base.hpp b/src/lib/zombie-check.hpp similarity index 80% rename from src/lib/dependable-base.hpp rename to src/lib/zombie-check.hpp index e33465202..79a707fc2 100644 --- a/src/lib/dependable-base.hpp +++ b/src/lib/zombie-check.hpp @@ -1,5 +1,5 @@ /* - DEPENDABLE-BASE.hpp - fundamental structures with extended lifespan + ZOMBIE-CHECK.hpp - flatliner self-detection Copyright (C) Lumiera.org 2018, Hermann Vosseler @@ -21,15 +21,16 @@ */ /** @file dependable-base.hpp - ** Static container to hold basic entities needed during static init and shutdown. + ** Detector to set off alarm when (re)using deceased objects. ** @see sync-classlock.hpp ** @see depend.hpp */ -#ifndef LIB_DEPENDABLE_BASE_H -#define LIB_DEPENDABLE_BASE_H +#ifndef LIB_ZOMBIE_CHECK_H +#define LIB_ZOMBIE_CHECK_H +#include "lib/del-stash.hpp" #include "lib/nocopy.hpp" #include @@ -54,8 +55,8 @@ namespace lib { } }; - ///////////////////////////////////////////////////////////////TICKET #1133 damn it. How the hell do we determine when the object is initialised...? - ///////////////////////////////////////////////////////////////TICKET #1133 ........ Looks like the whole approach is ill guided + template + uint Holder::accessed_; } // (End) nifty implementation details @@ -94,4 +95,4 @@ namespace lib { } // namespace lib -#endif /*LIB_DEPENDABLE_BASE_H*/ +#endif /*LIB_ZOMBIE_CHECK_H*/ diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 8519eea4f..68ad35335 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -28294,9 +28294,15 @@ - - - + + + + + + + + + @@ -28444,11 +28450,14 @@ + + - - + + + @@ -28457,29 +28466,172 @@ - - + + + + + - - + + + + + + + + + - - - - + + + + + - - + + + + + + - - - + + + + + + + + + + + + + + +

+ ...und das kann ziemlich indirekt passieren. +

+

+ Beispiel ist das ClassLock. Das ist ein Front-End, und verwendet verdeckt wieder einen Static. +

+

+ Und genau dafür gibt es anscheinend keine Garantieren +

+ + +
+ + + + + + + +

+ C++ hällt die Erzeugungs/Zerstörungs-Reihenfolge exakt ein +

+ + +
+
+ + + + + + +

+ d.h. wenn das local static später erzeugt wird, wird es vor  dem Hauptobjekt zerstört +

+ + +
+
+
+ + + + + + + + + + + + + + +

+ Statische Initialisierung funktioniert präzise, korrekt und zuverlässig +

+ + +
+
+ + + + + + +

+ Der Aufruf von Konstrukturen statischer Objekte konstituiert eine (dynamische) Reihenfolge. +

+

+ Desktuktoren werden exakt rückwärts in dieser Reihenfolge aufgerufen. +

+

+ Statische Objektfelder werden vor der ersten Verwendung der Klassendefinition  initialisiert +

+

+ Dagegen Funktions-lokale statische Variablen werden initialisiert, wenn der Kontrollfluß sie zum ersten mal berührt. +

+

+ Wenn ein Konstruktor ein statisches Feld verwendet, dann wird dieses Feld vor dem Konstruktor erzeugt. +

+

+ +

+

+ Beachte: in jedem dieser Fälle wird auch die o.g. Reihenfolge konstituiert. +

+

+ +

+

+ Corollar: wenn man ein Meyer's Singleton erst indirekt aus dem Implementierungs-Code verwendet, +

+

+ so wird es garantiert zerstört, bevor der Destruktor des aufrufenden Objekts läuft. +

+

+ Hallo ClassLock... +

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