vcl/source/app/svdata.cxx |   76 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 60 insertions(+), 16 deletions(-)

New commits:
commit c635271eb17e3b9d50223356dbc09b771fcd643f
Author:     Armin Le Grand (Collabora) <armin.le.gr...@me.com>
AuthorDate: Tue Nov 19 20:34:05 2024 +0100
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Wed Nov 20 16:53:11 2024 +0100

    tdf#163428 CairoSDPR: Fix regression in freeing unified buffers
    
    I had added BufferedData_ModifiedBitmapEx for CairoSDPR (but
    not only) to allow Bitmaps modualted by BColorModifierStack
    to be buffered in the standard way and at the original
    Bitmap. This nicely can still hold the system-dependent form
    of the Bitmap to be used at the Bitmap of that buffered
    modified Bitmap.
    This uses something I was not aware of: One Buffer implementation
    may - as in this case - use another buffer. That may lead to the
    second buffer being deleted/freed when the first one gets freed
    - by timer or directly - what is wanted. This *needs* the methods
    inside SystemDependentDataBuffer to do the actual potential
    destruction of the Object outside the Mutex-Locked parts of
    the code, else this might lead to a deadlock.
    It is good to have found this, there may be other such
    combinations in the future: That the implementation of a buffer
    using that standard mechanism uses another class that may also be
    buffered.
    I have now made sure that buffered objects get deleted *outside*
    the Mutex-Locked areas. This is better anyways, the destruction
    does not need that lock - it is just needed so secure the
    manipulation of the local unordered_map that organizes the
    buffered elements globally.
    
    Change-Id: Ie2f346a3f1a24105b89310ace3ff7a41472143c4
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176795
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>
    Reviewed-by: Moritz Duge <moritz.d...@allotropia.de>

diff --git a/vcl/source/app/svdata.cxx b/vcl/source/app/svdata.cxx
index fa80f3dbf127..98cd3c6da16f 100644
--- a/vcl/source/app/svdata.cxx
+++ b/vcl/source/app/svdata.cxx
@@ -140,13 +140,26 @@ namespace
 
         void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override
         {
-            std::unique_lock aGuard(m_aMutex);
-            EntryMap::iterator aFound(maEntries.find(rData));
+            // tdf#163428 prepare space for entry to hold to avoid early 
deletion
+            basegfx::SystemDependentData_SharedPtr aToBeDeletedEntry;
 
-            if(aFound != maEntries.end())
             {
-                maEntries.erase(aFound);
+                // lock m_aMutex in own scope
+                std::unique_lock aGuard(m_aMutex);
+                EntryMap::iterator aFound(maEntries.find(rData));
+
+                if(aFound != maEntries.end())
+                {
+                    // tdf#163428 ensure to hold/not delete entry while 
m_aMutex is locked
+                    aToBeDeletedEntry = aFound->first;
+
+                    // remove from list
+                    maEntries.erase(aFound);
+                }
             }
+
+            // tdf#163428  aToBeDeletedEntry will be destucted, thus the
+            // entry referenmced by SharedPtr may be deleted now
         }
 
         void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override
@@ -176,24 +189,55 @@ namespace
 
     IMPL_LINK_NOARG(SystemDependentDataBuffer, implTimeoutHdl, Timer *, void)
     {
-        std::unique_lock aGuard(m_aMutex);
-        EntryMap::iterator aIter(maEntries.begin());
+        // tdf#163428 prepare a temporary list for 
SystemDependentData_SharedPtr
+        // entries that need to be removed from the unordered_map, but do not 
need
+        // locking m_aMutex
+        ::std::vector<basegfx::SystemDependentData_SharedPtr> 
aToBeDeletedEntries;
 
-        while(aIter != maEntries.end())
         {
-            if(aIter->second)
-            {
-                aIter->second--;
-                ++aIter;
-            }
-            else
+            // lock m_aMutex in own scope
+            std::unique_lock aGuard(m_aMutex);
+            EntryMap::iterator aIter(maEntries.begin());
+
+            while(aIter != maEntries.end())
             {
-                aIter = maEntries.erase(aIter);
+                if(aIter->second)
+                {
+                    aIter->second--;
+                    ++aIter;
+                }
+                else
+                {
+                    // tdf#163428 Do not potentially directly delete 
SystemDependentData_SharedPtr
+                    // entries in the unordered_map (maEntries). That 
can/would happen when the shared_ptr
+                    // has only one reference left. This can cause problems, 
e.g. we have
+                    // BufferedData_ModifiedBitmapEx which can contain a 
Bitmap with added
+                    // DataBuffers itself, thus deleting this here *directly* 
would trigger e.g.
+                    // endUsage above. That would then block when we would 
still hold m_aMutex.
+                    // This can potentially be the case with other derivations 
of
+                    // basegfx::SystemDependentData, too.
+                    // To avoid this, protect the part that needs protection 
by the m_aMutex, that
+                    // is the modification of the unordered_map itself. 
Cleaning up the list entries can be
+                    // done after this, without holding the lock.
+                    // Thus, remember the SystemDependentData_SharedPtr in a 
temporary list by adding
+                    // a reference/shared_ptr to it to guarantee it gets not 
deleted when it gets removed
+                    // from the unordered_map.
+                    // Anyways, only locking while manipulating the list is 
better, destruction of
+                    // objects may be expensive and hold m_aMutex longer than 
necessary.
+                    aToBeDeletedEntries.push_back(aIter->first);
+
+                    // remove from list. This decrements the shared_ptr, but 
delete is avoided
+                    aIter = maEntries.erase(aIter);
+                }
             }
+
+            if (maEntries.empty())
+                maTimer->Stop();
         }
 
-        if (maEntries.empty())
-            maTimer->Stop();
+        // tdf#163428 here aToBeDeletedEntries will be destroyed, the entries 
will be
+        // decremented and potentially deleted. These are of type 
SystemDependentData_SharedPtr,
+        // so we do not need to do anything explicitely here
     }
 }
 

Reply via email to