From 21fdce0dfc923fd19e06d9c7802b5abe60cc5a7f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 1 Apr 2018 04:43:43 +0200 Subject: [PATCH] a better solution to reject out-of-order static access after shutdown Static initialisation and shutdown can be intricate; but in fact they work quite precise and deterministic, once you understand the rules of the game. In the actual case at hand the ClassLock was already destroyed, and it must be destroyed at that point, according to the standard. Simply because it is created on-demand, *after* the initialisation of the static DependencyFactory, which uses this lock, and so its destructor must be called befor the dtor of DependencyFactory -- which is precisely what happens. So there is no need to establish a special secure "base runtime system", and this whole idea is ill-guided. I'll thus close ticket #1133 as wontfix Conflicts: src/lib/dependable-base.hpp --- doc/design/architecture/Subsystems.txt | 24 ++- src/lib/depend.hpp | 12 ++ .../{dependable-base.hpp => zombie-check.hpp} | 15 +- wiki/thinkPad.ichthyo.mm | 192 ++++++++++++++++-- 4 files changed, 215 insertions(+), 28 deletions(-) rename src/lib/{dependable-base.hpp => zombie-check.hpp} (80%) 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... +

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