memory: Construct globals on first use #15266

pull MarcoFalke wants to merge 1 commits into bitcoin:master from MarcoFalke:Mf1901-cofu changing 6 files +34 −28
  1. MarcoFalke commented at 9:01 PM on January 25, 2019: member

    The (de)initialization order is not well defined in C++, so generally it is not safe to use globals as the (de/con)structor of one global could use the (de/con)structor of another global before/after it has been (con/de)structed.

    Specifically this fixes:

    • g_logger might not be initialized on the first use, so do that. (Fixes #15111)
  2. MarcoFalke force-pushed on Jan 25, 2019
  3. MarcoFalke added the label Refactoring on Jan 25, 2019
  4. MarcoFalke added the label Utils/log/libs on Jan 25, 2019
  5. sipa commented at 9:22 PM on January 25, 2019: member

    Just as a way to minimize the diff (which may or may not be desirable): you can have a trivial proxy object instantiated as g_logger whose operator->() invokes GetLogger().

  6. MarcoFalke force-pushed on Jan 25, 2019
  7. promag commented at 11:15 PM on January 25, 2019: member

    An alternative is to use templating and also defer instantiation to operator->(). This way the diff would be smaller and the code less verbose.

    <details> ```cpp #include <string> #include <iostream>

    using namespace std;

    template <typename T> class COFU { T* get() { static T v; return &v; }

    public: T* operator->() { return get(); } T* const operator->() const { return const_cast<COFU<T>>(this)->get(); } T& operator() { return get(); } T& operator() const { return const_cast<COFU<T>>(this)->get(); } };

    string* const g_str_old = new string(); COFU<string> const g_str_new;

    int main() { g_str_old->append("Hello!"); g_str_new->append("Hello!");

    cout << *g_str_old << endl; cout << *g_str_new << endl;

    return 0; }

    </details>
    Edit: oh [@sipa](/bitcoin-bitcoin/contributor/sipa/) already suggested above.
    
  8. in src/sync.cpp:70 in fadf76a166 outdated
      77 |  };
      78 | -LockData& GetLockData() {
      79 | -    static LockData lockdata;
      80 | -    return lockdata;
      81 | -}
      82 | +static GLOBAL_COFU(LockData, GetLockData);
    


    promag commented at 11:17 PM on January 25, 2019:

    static 👍

  9. MarcoFalke commented at 12:41 AM on January 26, 2019: member

    COFU<string> const g_str_new;

    It looks like you are introducing a new global for this, which again opens up the problem I am trying to solve. Either I am missing something or we are running in circles.

  10. promag commented at 12:46 AM on January 26, 2019: member

    @MarcoFalke my suggestion is to do:

    COFU<BCLog::Logger> const g_logger;
    

    so that the following is unchanged:

    g_logger->m_reopen_file = true;
    

    Like @sipa suggested.

  11. sipa commented at 12:51 AM on January 26, 2019: member

    @MarcoFalke Not sure if that's what you're referring to, but the COFU object has no member variables to initialize, so it doesn't have any runtime initialization (in other words, it's fully constructed before any code is executed).

    I don't have a strong opinion on whether such a COFU object/type is the right solution; just offering it as a possibility if reducing the size of the diff is wanted.

  12. MarcoFalke commented at 12:52 AM on January 26, 2019: member

    Ah, that makes it clearer.

  13. sipa commented at 12:55 AM on January 26, 2019: member

    Relevant section from https://en.cppreference.com/w/cpp/language/constant_initialization:

    The effects of constant initialization are the same as the effects of the corresponding initialization, except that it's guaranteed that it is complete before any other initialization of a static or thread-local object begins, and it may be performed at compile time.

  14. DrahtBot commented at 1:12 AM on January 26, 2019: member

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #15329 (Fix InitError() and InitWarning() content by hebasto)
    • #14169 (add -debuglogsize=<n> option by SuckShit)
    • #13746 (-masterdatadir for datadir bootstrapping by kallewoof)
    • #13088 (Log early messages with -printtoconsole by ajtowns)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  15. MarcoFalke force-pushed on Jan 26, 2019
  16. MarcoFalke force-pushed on Jan 26, 2019
  17. in src/sync.cpp:8 in fad73c1474 outdated
       4 | @@ -5,6 +5,7 @@
       5 |  #include <sync.h>
       6 |  
       7 |  #include <logging.h>
       8 | +#include <util/memory.h>
    


    AkioNak commented at 3:31 AM on January 26, 2019:

    nit: You can delete `#include <memory>` after a few lines.


    promag commented at 3:18 PM on January 27, 2019:

    Any reason to keep the mentioned include?


    MarcoFalke commented at 3:25 PM on January 27, 2019:

    It has been there before, so according to iwyu it should stay there. I'd prefer if someone jotted down some notes on how to run iwyu on Bitcoin Core, so that we wouldn't have to spend review on those.


    AkioNak commented at 1:41 AM on January 28, 2019:

    In this case, I think it was already unnecessary to include, and good time to remove.

    1. #include <memory> has been added to sync.cpp because it already using std::unique_ptr ( #12859).
    2. std::unique_ptr has been removed from sync.cpp (#13300)
  18. in src/sync.cpp:106 in fad73c1474 outdated
     102 | @@ -113,7 +103,7 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch,
     103 |  
     104 |  static void push_lock(void* c, const CLockLocation& locklocation)
     105 |  {
     106 | -    LockData& lockdata = GetLockData();
     107 | +    LockData& lockdata{*g_lockdata.Get()};
    


    AkioNak commented at 4:08 AM on January 26, 2019:

    I prefer following code rather than calling Get() directly.

        LockData& lockdata = *g_lockdata;
    

    or

        LockData& lockdata{*g_lockdata};
    
  19. in src/sync.cpp:171 in fad73c1474 outdated
     167 | @@ -178,11 +168,7 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi
     168 |  
     169 |  void DeleteLock(void* cs)
     170 |  {
     171 | -    LockData& lockdata = GetLockData();
     172 | -    if (!lockdata.available) {
     173 | -        // We're already shutting down.
     174 | -        return;
     175 | -    }
     176 | +    LockData& lockdata{*g_lockdata.Get()};
    


    AkioNak commented at 4:11 AM on January 26, 2019:

    (same above) I prefer following code rather than calling Get() directly.

        LockData& lockdata = *g_lockdata;
    

    or

        LockData& lockdata{*g_lockdata};
    
  20. in src/sync.cpp:117 in fad73c1474 outdated
     102 | @@ -113,7 +103,7 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch,
     103 |  
     104 |  static void push_lock(void* c, const CLockLocation& locklocation)
     105 |  {
     106 | -    LockData& lockdata = GetLockData();
     107 | +    LockData& lockdata{*g_lockdata.Get()};
     108 |      std::lock_guard<std::mutex> lock(lockdata.dd_mutex);
    


    promag commented at 2:06 PM on January 26, 2019:

    Another alternative is to ditch local lockdata and use global g_lockdata:

    std::lock_guard<std::mutex> lock(g_lockdata->dd_mutex);
    

    Would result in a bigger diff but I think it is better than "aliases".

  21. MarcoFalke force-pushed on Jan 26, 2019
  22. MarcoFalke force-pushed on Jan 27, 2019
  23. in src/logging.cpp:25 in faacd53015 outdated
      21 | @@ -21,7 +22,7 @@ const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
      22 |   * This method of initialization was originally introduced in
      23 |   * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
      24 |   */
      25 | -BCLog::Logger* const g_logger = new BCLog::Logger();
      26 | +GlobalCOFU<BCLog::Logger> g_logger;
    


    promag commented at 3:17 PM on January 27, 2019:

    Could update comment above to note that the raw pointer is now in GlobalCOFU?

  24. promag commented at 3:21 PM on January 27, 2019: member

    ACK faacd53.

  25. in src/util/memory.h:20 in faacd53015 outdated
      15 | @@ -16,4 +16,20 @@ std::unique_ptr<T> MakeUnique(Args&&... args)
      16 |      return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
      17 |  }
      18 |  
      19 | +/**
      20 | + * Ensure that a global is initialized before its first use and never
    


    sipa commented at 12:21 AM on January 28, 2019:

    This no-destruction property shouldn't be needed. C++ guarantees that destructors are invoked at shutdown in reverse order as they were constructed (even taking objects constructed through functions invoked from other module's global initializers into account).

    Have you found evidence that this isn't enough?


    MarcoFalke commented at 6:16 PM on January 28, 2019:

    @sipa, see the comments in sync and logging. Basically the order in what you need the destructor to be called might be different from the inverse order they were constructed.

    See a simple example that segfaults if I dropped that requirement:

    (The ~Init destructor uses the log after the logger has been destructed)

    #include <iostream>
    #include <memory>
    struct Log{
     std::unique_ptr<const std::string> m_prefix;
     Log(){m_prefix=std::unique_ptr<const std::string>(new std::string{"::: "});log(__func__);};
     ~Log(){log(__func__);};
     void log(const std::string&s){std::cout << *m_prefix << s << std::endl;}
    };
    
    Log& LogInstance() {
     static Log l;
     return l;
    }
    
    struct Init{
     Init(){std::cout << __func__<<std::endl;}
     ~Init(){LogInstance().log(__func__);}
    } g_init;
    
    int main()
    {
     LogInstance().log(__func__);
    }
    

    promag commented at 6:21 PM on January 28, 2019:

    @MarcoFalke because Init is constructed first. It works if you change Init constructor to Init(){LogInstance().log(__func__);}.


    sipa commented at 6:22 PM on January 28, 2019:

    @MarcoFalke I think that can be resolved by calling LogInstance in the Init::Init constructor once. That forces the Log object to finished construction before Init, guaranteeing it's still available at Init's destruction time.


    MarcoFalke commented at 6:27 PM on January 28, 2019:

    I know, but there is no way to enforce that (in Bitcoin Core or generally) the logger (or any other global "module") is called in each constructor when the destructor calls it.


    sipa commented at 6:30 PM on January 28, 2019:

    I really prefer going over the few global objects whose destructors need other globals and fixing this, than having lingering global objects at shutdown.


    promag commented at 6:31 PM on January 28, 2019:

    Just make a call to LogInstance()?

  26. in src/util/memory.h:24 in faacd53015 outdated
      15 | @@ -16,4 +16,20 @@ std::unique_ptr<T> MakeUnique(Args&&... args)
      16 |      return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
      17 |  }
      18 |  
      19 | +/**
      20 | + * Ensure that a global is initialized before its first use and never
      21 | + * destructed, since other destructors might use it.
      22 | + * This class should not be used where the destructor ~T() is non-trivial.
      23 | + */
      24 | +template <typename T>
    


    sipa commented at 12:23 AM on January 28, 2019:

    Perhaps also add a comment to explain that if multiple instances of GlobalCOFU<T> for the same T exist, they will all share the underlying T object.

  27. in src/util/memory.h:25 in faacd53015 outdated
      20 | + * Ensure that a global is initialized before its first use and never
      21 | + * destructed, since other destructors might use it.
      22 | + * This class should not be used where the destructor ~T() is non-trivial.
      23 | + */
      24 | +template <typename T>
      25 | +struct GlobalCOFU {
    


    ryanofsky commented at 4:38 PM on January 28, 2019:

    This seems over-engineered. Why not just have a simple function like:

    Logger& LogInstance() {
      static Logger logger;
      return logger;
    }
    

    and have a scripted diff replacing g_logger-> with LogInstance(). This would be equivalent to what the GlobalCOFU class is doing but more straightforward, and would actually guarantee destruction, and avoid creating landmines where separate variables of the same type point to the same instances, and avoid weird terminology (COFU == singleton?)


    promag commented at 5:02 PM on January 28, 2019:

    and would actually guarantee destruction

    FWIW the same could be done with GlobalCOFU.

  28. MarcoFalke force-pushed on Jan 29, 2019
  29. MarcoFalke force-pushed on Jan 29, 2019
  30. MarcoFalke force-pushed on Jan 29, 2019
  31. MarcoFalke commented at 7:20 PM on January 29, 2019: member

    Reverted back to my initial patch (using the LogInstance wording suggested here #15266 (review))

  32. in src/logging.cpp:28 in fa8e2b75ff outdated
      20 | @@ -21,7 +21,11 @@ const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
      21 |   * This method of initialization was originally introduced in
      22 |   * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
      23 |   */
      24 | -BCLog::Logger* const g_logger = new BCLog::Logger();
      25 | +BCLog::Logger& LogInstance()
      26 | +{
      27 | +    static BCLog::Logger* g_logger{new BCLog::Logger()};
    


    ryanofsky commented at 7:37 PM on January 29, 2019:

    Would add comment here that this intentionally never calls logger destructor. Would also add a comment in the logger class saying that if a destructor is defined there it will never be called, and that any members added to the logger class will not have their destructors called either.


    MarcoFalke commented at 8:31 PM on January 29, 2019:

    The comment above says that, I think. Though I added a line clarifying that it must be a trivial destructor.

  33. ryanofsky approved
  34. ryanofsky commented at 7:40 PM on January 29, 2019: member

    utACK fa8e2b75ff7de6b09f97512256092c1d03edef01

  35. log: Construct global logger on first use 77777c5624
  36. MarcoFalke force-pushed on Jan 29, 2019
  37. MarcoFalke added this to the milestone 0.18.0 on Feb 1, 2019
  38. ryanofsky approved
  39. ryanofsky commented at 6:33 PM on February 4, 2019: member

    utACK 77777c5624e2f5416d85500e82b7c80e10ed01b6. Just comment change since last review. Also the commit hash no longer begins with fa (in case that is a problem).

  40. jonasschnelli commented at 7:07 PM on February 4, 2019: contributor

    utACK 77777c5624e2f5416d85500e82b7c80e10ed01b6

  41. MarcoFalke referenced this in commit 452acee4da on Feb 4, 2019
  42. MarcoFalke merged this on Feb 4, 2019
  43. MarcoFalke closed this on Feb 4, 2019

  44. MarcoFalke deleted the branch on Feb 4, 2019
  45. laanwj referenced this in commit e50853501b on Feb 5, 2019
  46. vijaydasmp referenced this in commit 0d276489a9 on Sep 12, 2021
  47. vijaydasmp referenced this in commit 922972dd32 on Sep 12, 2021
  48. vijaydasmp referenced this in commit 9b594e9540 on Sep 13, 2021
  49. vijaydasmp referenced this in commit c9c082553c on Sep 13, 2021
  50. vijaydasmp referenced this in commit b9f2bef379 on Sep 13, 2021
  51. vijaydasmp referenced this in commit 5b5537b8f4 on Sep 14, 2021
  52. vijaydasmp referenced this in commit bcbf3388d1 on Sep 14, 2021
  53. vijaydasmp referenced this in commit 15f14806a8 on Sep 16, 2021
  54. PastaPastaPasta referenced this in commit 78e04b92f2 on Sep 16, 2021
  55. MarcoFalke locked this on Dec 16, 2021

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-13 15:15 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me