Date: prev next · Thread: first prev next last
2011 Archives by date, by thread · List index


I was investigating why the osl_Thread unit test (sal/qa/osl/process/osl_Thread.cxx) fails most of 
the times on Windows. If I understand correctly, the problem is a quite fundamental difference in 
the semantics of thread-specific data keys created with a destructor callback passed to 
osl_createrThreadKey() (or its wrapper, the constructor of the ThreadData class):

On Windows, if the value of a key is changed with osl_setThreadKeyData() (or its wrapper, 
ThreadData::setData()), the destructor callbac is called for the old value. (See the code in 
sal/osl/w32/thread.c. On Unix this does not seem to happen. There osl_setThreadKeyData() is just a 
thin wrapper for pthread_setspecific(), and the documentation for that does not say anything about 
the destructor being called. (Experimentation confirms this, at least on Linux.)

Now, this means that on Linux, if you set a thread-specific key's value to some pointer to 
dynamically allocated memory, and then set it to another value, the first value will indeed leak 
unless you otherwise free it yourself. This is why Caolán added some explicit delete calls in 
April. But at least some of those delete calls now then cause double deletes of the same data on 
Windows, causing to slight (non-fatal in this case) heap corruption, and in most runs of the test 
then (apparently a bit timing dependent) a failed assertion.

So, the interesting question now then is whether this difference in  osl_setThreadKeyData() 
semantics is intentional and known, and handled specifically in those places in the code where 
thread-specific data is used? Or whether the code assumes the semantics on Windows and just then 
leaks on Unix? Or what...

Here is a patch that adds some debugging output to osl_Thread.cxx:

diff --git a/sal/qa/osl/process/osl_Thread.cxx b/sal/qa/osl/process/osl_Thread.cxx
index aa6ad67..982a82e
--- a/sal/qa/osl/process/osl_Thread.cxx
+++ b/sal/qa/osl/process/osl_Thread.cxx
@@ -1896,7 +1896,8 @@ namespace osl_Thread
 // destroy function when the binding thread terminate
 void SAL_CALL destroyCallback(void * data)
 {
-    delete[] (char *) data;
+    fprintf (stderr, "del: %p (data in destroyCallback)\n", data); fflush (stderr);
+    //delete[] (char *) data;
 }
 
 static ThreadData myThreadData(destroyCallback);
@@ -1919,6 +1920,7 @@ private:
     void SAL_CALL run()
         {
             char * pc = new char[2];
+            fprintf (stderr, "new: %p (pc in myKeyThread::run)\n", pc); fflush (stderr);
 //      strcpy(pc, &m_nData);
             memcpy(pc, &m_nData, 1);
             pc[1] = '\0';
@@ -2032,6 +2034,7 @@ namespace osl_ThreadData
             {
                 // at first, set the data a value
                 char* pc = new char[2];
+                fprintf (stderr, "new: %p (pc in setData_002)\n", pc); fflush (stderr);
                 char m_nData = 'm';
 // LLA: this is a copy functions only and really only for \0 terminated strings
 //      m_nData is not a string, it's a character
@@ -2068,6 +2071,7 @@ namespace osl_ThreadData
             {
                 // at first, set the data a value
                 char* pc = new char[2];
+                fprintf (stderr, "new: %p (pc in setData_003)\n", pc); fflush (stderr);
                 char m_nData = 'm';
                 memcpy(pc, &m_nData, 1);
                 pc[1] = '\0';
@@ -2080,6 +2084,7 @@ namespace osl_ThreadData
                 // aThread1 and aThread2 should have not terminated yet
                 // setData the second time
                 char* pc2 = new char[2];
+                fprintf (stderr, "new: %p (pc2 in setData_003)\n", pc2); fflush (stderr);
                 m_nData = 'o';
                 memcpy(pc2, &m_nData, 1);
                 pc2[1] = '\0';
@@ -2100,8 +2105,10 @@ namespace osl_ThreadData
                     cData1 == 'a' && cData2 == 'b' && aChar == 'o'
                     );
 
-                delete [] pc2;
-                delete [] pc;
+                fprintf (stderr, "del: %p (pc2 in setData_003\n", pc2); fflush (stderr);
+                // delete [] pc2;
+                fprintf (stderr, "del: %p (pc in setData_003)\n", pc); fflush (stderr);
+                // delete [] pc;
             }
 
         CPPUNIT_TEST_SUITE(setData);
@@ -2127,6 +2134,7 @@ namespace osl_ThreadData
         void getData_001()
             {
                 char* pc = new char[2];
+                fprintf (stderr, "new: %p (pc in getData_001)\n", pc); fflush (stderr);
                 char m_nData[] = "i";
                 strcpy(pc, m_nData);
                 myThreadData.setData(pc);
@@ -2150,7 +2158,8 @@ namespace osl_ThreadData
                     cData1 == 'c' && cData2 == 'd' && aChar == 'i'
                     );
 
-                delete [] pc;
+                fprintf (stderr, "del: %p (pc in getData_001)\n", pc); fflush (stderr);
+                // delete [] pc;
             }
 
         // setData then change the value in the address data pointer points,
@@ -2158,6 +2167,7 @@ namespace osl_ThreadData
         void getData_002()
             {
                 char* pc = new char[2];
+                fprintf (stderr, "new: %p (pc in getData_002)\n", pc); fflush (stderr);
                 char m_nData = 'i';
                 memcpy(pc, &m_nData, 1);
                 pc[1] = '\0';

As you see I coment out the actual delete[] calls, so that the heap blocks are not reused and the 
history of each relevant heap block can be seen more uniquely in the output. When run on Windows, I 
get:

new: 004AED08 (pc in setData_002)
new: 004AED18 (pc in myKeyThread::run)
new: 004AED28 (pc in myKeyThread::run)
del: 004AED18 (data in destroyCallback)
del: 004AED28 (data in destroyCallback)
new: 004AED38 (pc in setData_003)
del: 004AED08 (data in destroyCallback)
new: 004AED48 (pc2 in setData_003)
del: 004AED38 (data in destroyCallback)  <<<===
new: 004AED58 (pc in myKeyThread::run)
new: 004AED68 (pc in myKeyThread::run)
del: 004AED68 (data in destroyCallback)
del: 004AED58 (data in destroyCallback)
del: 004AED48 (pc2 in setData_003        <<<===
del: 004AED38 (pc in setData_003)        <<<===
new: 004AED78 (pc in getData_001)
del: 004AED48 (data in destroyCallback)  <<<===
new: 004AED88 (pc in myKeyThread::run)
new: 004AED98 (pc in myKeyThread::run)
del: 004AED88 (data in destroyCallback)
del: 004AED98 (data in destroyCallback)
del: 004AED78 (pc in getData_001)        <<<===
new: 004AEDA8 (pc in getData_002)
del: 004AED78 (data in destroyCallback)  <<====
new: 004AEDB8 (pc in myKeyThread::run)
new: 004A3C38 (pc in myKeyThread::run)
del: 004A3C38 (data in destroyCallback)
del: 004AEDB8 (data in destroyCallback)

Note the indicated places where the same pointer gets deleted twice.

When run on Linux, I see no such problems.

--tml


Context


Privacy Policy | Impressum (Legal Info) | Copyright information: Unless otherwise specified, all text and images on this website are licensed under the Creative Commons Attribution-Share Alike 3.0 License. This does not include the source code of LibreOffice, which is licensed under the Mozilla Public License (MPLv2). "LibreOffice" and "The Document Foundation" are registered trademarks of their corresponding registered owners or are in actual use as trademarks in one or more countries. Their respective logos and icons are also subject to international copyright laws. Use thereof is explained in our trademark policy.