Request: bypass RunningThreadsList in Thread ctor


#1

I have some framework / library code that creates a couple of threads during the application’s initialization of objects with static storage duration. Specifically, I have facilities that provide a “once per second” call for registered clients, as well as a reference counted, thread-safe singleton base class. There are some instances of reference counted singletons which themselves create threads. The singletons are destroyed in the reverse order of creation, and guaranteed to get deleted “last” on program exit, as long as the usage rules are followed. One of these rules is that all objects with static storage duration that have non-trivial constructors or destructors must use the reference counted singleton system (so it can keep track of them all).

Unfortunately, I cannot use a juce::Thread for this library code because it is not guaranteed that the destructor for the global RunningThreadsList (juce_Thread.cpp: getInstance()) will execute after the destructors for my singleton objects. Therefore, I get the assert and things rapidly go south.

I’m using boost::thread for these threads but I would like to get rid of the dependency on boost (I have it down to boost::thread, and boost::thread_specific_ptr). This change (with a suitable implementation) would solve the problem:

juce_Thread.h

  explicit Thread (const String& threadName, bool addToRunningThreadsList = true);

The alternative is for Juce to provide truly robust facilities for reference counted, thread-safe singletons. This would require a bit more framework and still doesn’t provide the functionality for avoiding the RunningThreadsList. For analysis, this is my implementation for reference counted, thread safe singletons:

class SingletonLifetime
{
public:
  enum Lifetime
  {
    // Singleton is created on first use and destroyed when
    // the last reference is removed.
    //
    createOnDemand,

    // Like createOnDemand, but after the Singleton is destroyed an
    // exception will be thrown if an attempt is made to create it again.
    //
    createOnDemandOnce,

    // The singleton is created on first use and persists until program exit.
    //
    persistAfterCreation
  };
};

template <class Object>
class SharedSingleton : private PerformedAtExit
{
protected:
  explicit SharedSingleton (SingletonLifetime::Lifetime const lifetime)
    : PerformedAtExit (lifetime == SingletonLifetime::persistAfterCreation)
    , m_lifetime (lifetime)
  {
    vfassert (s_instance == nullptr);

    if (m_lifetime == SingletonLifetime::persistAfterCreation)
    {
      incReferenceCount ();
    }
    else if (m_lifetime == SingletonLifetime::createOnDemandOnce && *s_created)
    {
      Throw (Error().fail (__FILE__, __LINE__));
    }

    *s_created = true;
  }

  virtual ~SharedSingleton ()
  {
    vfassert (s_instance == nullptr);
  }

public:
  typedef SharedObjectPtr <Object> Ptr;

  static Ptr getInstance ()
  {
    Ptr instance;

    instance = s_instance;

    if (instance == nullptr)
    {
      SpinLock::ScopedLockType lock (*s_mutex);

      instance = s_instance;
  
      if (instance == nullptr)
      {
        s_instance = Object::createInstance ();

        instance = s_instance;
      }
    }

    return instance;
  }

  inline void incReferenceCount() noexcept
  {
    m_refs.addref ();
  }

  inline void decReferenceCount() noexcept
  {
    vfassert (m_refs.is_signaled ());

    if (m_refs.release ())
      destroySingleton ();
  }

  // Caller must synchronize.
  inline bool isBeingReferenced () const
  {
    return m_refs.is_signaled ();
  }

private:
  void performAtExit ()
  {
    decReferenceCount ();
  }

  void destroySingleton ()
  {
    bool destroy;

    {
      SpinLock::ScopedLockType lock (*s_mutex);

      if (isBeingReferenced ())
      {
        destroy = false;
      }
      else
      {
        destroy = true;
        s_instance = 0;
      }
    }

    if (destroy)
    {
      delete this;
    }
  }

private:
  SingletonLifetime::Lifetime const m_lifetime;
  Atomic::Counter m_refs;

private:
  static Object* s_instance;
  static Static::Storage <SpinLock, SharedSingleton <Object> > s_mutex;
  static Static::Storage <bool, SharedSingleton <Object> > s_created;
};

It is quite a different design philosophy from the Juce implementation, and also relies on some C++ language specifications (class Static::Storage).