Deleting on the message thread


#1

Hello! More experimentation time!

Lets say I have an object type which I want to try to ensure is only deleted from the message thread...

How offensive is this approach?

- A templated singleton which can add objects to be destroyed via AsyncUpdater (and automatically flushes on shutdown, or on request)...

///////////////////////////////////////////////////////////////////////////////
/**
    Auto-deleted singleton to asynchronously destroy objects (on the message
    thread) if they are deleted from another thread.
    [NOTE: slightly abbreviated for forum clarity!]
*/
///////////////////////////////////////////////////////////////////////////////
template <class ObjectType>
class MessageThreadDeleteList    :    public juce::DeletedAtShutdown,
                                      private juce::AsyncUpdater
{
public:
    
    MessageThreadDeleteList () {}
    ~MessageThreadDeleteList ()
    {
        flush ();
        getInstanceHolder().clear (false);
    }
    void flush ()
    {
        if (juce::MessageManager::getInstance()->isThisTheMessageThread())
        {
            objectsToDelete.clearQuick (true);
        }
    }
    void addObject (ObjectType* object)
    {
        jassert (!objectsToDelete.contains(object));
        objectsToDelete.add (object);
        triggerAsyncUpdate ();
    }
    static void deleteAsynchronously (ObjectType* object)
    {
        if (object != nullptr)
        {
            MessageThreadDeleteList< ObjectType >* instance = getInstance ();
            if (instance != nullptr)
            {
                instance->addObject (object);
            }
        }
    }
    static void defaultDestroy (ObjectType* object)
    {
        if (juce::MessageManager::getInstance()->isThisTheMessageThread())
        {
            delete object;
        }
        else
        {
            deleteAsynchronously (object);
        }
    }
    static MessageThreadDeleteList* getInstance ()
    {
        return getInstanceHolder().getInstance ();
    }
private:
    virtual void handleAsyncUpdate () override
    {
        flush ();
    }

    struct InstanceHolder
    {
        juce::SpinLock lock;
        juce::ScopedPointer< MessageThreadDeleteList< ObjectType > > instance;
        void clear (bool destroy = true)
        {
            const juce::SpinLock::ScopedLockType sl (lock);
            if (!destroy)
            {
                instance.release ();
            }
            else
            {
                instance = nullptr;
            }
        }
        MessageThreadDeleteList< ObjectType >* getInstance ()
        {
            const juce::SpinLock::ScopedLockType sl (lock);
            if (instance == nullptr)
            {
                instance = new MessageThreadDeleteList< ObjectType >();
            }
            return instance;
        }
    };
    static InstanceHolder& getInstanceHolder() noexcept
    {
        static void* holder [(sizeof (InstanceHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 };
        return *reinterpret_cast<InstanceHolder*> (holder);
    }

    juce::OwnedArray< ObjectType > objectsToDelete;
};

- A ContainerDeletePolicy to use this...

template <>
struct juce::ContainerDeletePolicy< SomeClass >
{
    static void destroy (SomeClass* object)
    {
        if (juce::MessageManager::getInstance()->isThisTheMessageThread())
        {
            delete object;
        }
        else
        {
            MessageThreadDeleteList< SomeClass >::deleteAsynchronously (object);
        }
    }
};

Obviously this policy will only work for containers specifically using that class (and not subclasses, unless they define the same policy), but beyond that... is it something that would get the police on my case?


#2

Yes, I've used similar patterns, e.g. in tracktion we move plugin objects to a list that deletes them after a short delay.