Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Changeset 487

Show
Ignore:
Timestamp:
01/08/11 19:35:23 (14 years ago)
Author:
sean
Message:

Made a few more changes (explained in comments) to deal with deadlocks and other bad behavior caused by badly timed collections. This new approach works for the test code in bugzilla 4890, plus the other test cases I've found to cause similar problems.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/src/core/thread.d

    r486 r487  
    150150 
    151151            assert( obj.m_curr is &obj.m_main ); 
    152152            obj.m_main.bstack = getStackBottom(); 
    153153            obj.m_main.tstack = obj.m_main.bstack; 
    154154 
    155155            void* pstart = cast(void*) &_tlsstart; 
    156156            void* pend   = cast(void*) &_tlsend; 
    157157            obj.m_tls = pstart[0 .. pend - pstart]; 
    158158 
    159159            Thread.setThis( obj ); 
    160             Thread.add( obj ); 
     160            //Thread.add( obj ); 
    161161            scope( exit ) 
    162162            { 
    163163                Thread.remove( obj ); 
    164164            } 
    165165            Thread.add( &obj.m_main ); 
    166166 
    167167            // NOTE: No GC allocations may occur until the stack pointers have 
    168168            //       been set and Thread.getThis returns a valid reference to 
    169169            //       this thread object (this latter condition is not strictly 
    170170            //       necessary on Windows but it should be followed for the 
     
    353353            } 
    354354            else 
    355355            { 
    356356                auto pstart = cast(void*) &_tlsstart; 
    357357                auto pend   = cast(void*) &_tlsend; 
    358358                obj.m_tls = pstart[0 .. pend - pstart]; 
    359359            } 
    360360             
    361361            obj.m_isRunning = true; 
    362362            Thread.setThis( obj ); 
    363             Thread.add( obj ); 
     363            //Thread.add( obj ); 
    364364            scope( exit ) 
    365365            { 
    366366                // NOTE: isRunning should be set to false after the thread is 
    367367                //       removed or a double-removal could occur between this 
    368368                //       function and thread_suspendAll. 
    369369                Thread.remove( obj ); 
    370370                obj.m_isRunning = false; 
    371371            } 
    372372            Thread.add( &obj.m_main ); 
    373373             
     
    785785            pthread_attr_t  attr; 
    786786 
    787787            if( pthread_attr_init( &attr ) ) 
    788788                throw new ThreadException( "Error initializing thread attributes" ); 
    789789            if( m_sz && pthread_attr_setstacksize( &attr, m_sz ) ) 
    790790                throw new ThreadException( "Error initializing thread stack size" ); 
    791791            if( pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ) ) 
    792792                throw new ThreadException( "Error setting thread joinable" ); 
    793793        } 
    794794 
    795         version( Windows ) 
    796         { 
    797             m_hndl = cast(HANDLE) _beginthreadex( null, m_sz, &thread_entryPoint, cast(void*) this, 0, &m_addr ); 
    798             if( cast(size_t) m_hndl == 0 ) 
    799                 throw new ThreadException( "Error creating thread" ); 
    800         } 
    801         else version( Posix ) 
    802         { 
    803             // NOTE: This is also set to true by thread_entryPoint, but set it 
    804             //       here as well so the calling thread will see the isRunning 
    805             //       state immediately. 
    806             m_isRunning = true; 
    807             scope( failure ) m_isRunning = false; 
    808  
    809             if( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) 
    810                 throw new ThreadException( "Error creating thread" ); 
    811         } 
    812         version( OSX ) 
    813         { 
    814             m_tmach = pthread_mach_thread_np( m_addr ); 
    815             if( m_tmach == m_tmach.init ) 
    816                 throw new ThreadException( "Error creating thread" ); 
     795        // NOTE: The starting thread must be added to the global thread list 
     796        //       here rather than within thread_entryPoint to prevent a race 
     797        //       with the main thread, which could finish and terminat the 
     798        //       app without ever knowing that it should have waited for this 
     799        //       starting thread.  In effect, not doing the add here risks 
     800        //       having thread being treated like a daemon thread. 
     801        synchronized( slock ) 
     802        { 
     803            version( Windows ) 
     804            { 
     805                m_hndl = cast(HANDLE) _beginthreadex( null, m_sz, &thread_entryPoint, cast(void*) this, 0, &m_addr ); 
     806                if( cast(size_t) m_hndl == 0 ) 
     807                    throw new ThreadException( "Error creating thread" ); 
     808            } 
     809            else version( Posix ) 
     810            { 
     811                // NOTE: This is also set to true by thread_entryPoint, but set it 
     812                //       here as well so the calling thread will see the isRunning 
     813                //       state immediately. 
     814                m_isRunning = true; 
     815                scope( failure ) m_isRunning = false; 
     816 
     817                if( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) 
     818                    throw new ThreadException( "Error creating thread" ); 
     819            } 
     820            version( OSX ) 
     821            { 
     822                m_tmach = pthread_mach_thread_np( m_addr ); 
     823                if( m_tmach == m_tmach.init ) 
     824                    throw new ThreadException( "Error creating thread" ); 
     825            } 
     826            add( this ); 
    817827        } 
    818828    } 
    819829 
    820830 
    821831    /** 
    822832     * Waits for this thread to complete.  If the thread terminated as the 
    823833     * result of an unhandled exception, this exception will be rethrown. 
    824834     * 
    825835     * Params: 
    826836     *  rethrow = Rethrow any unhandled exception which may have caused this 
     
    16101620    // Add a context to the global context list. 
    16111621    // 
    16121622    static void add( Context* c ) 
    16131623    in 
    16141624    { 
    16151625        assert( c ); 
    16161626        assert( !c.next && !c.prev ); 
    16171627    } 
    16181628    body 
    16191629    { 
    1620         synchronized( slock ) 
    1621         { 
    1622             if( sm_cbeg ) 
    1623             { 
    1624                 c.next = sm_cbeg; 
    1625                 sm_cbeg.prev = c; 
    1626             } 
    1627             sm_cbeg = c; 
    1628             ++sm_clen; 
     1630        // NOTE: This loop is necessary to avoid a race between newly created 
     1631        //       threads and the GC.  If a collection starts between the time 
     1632        //       Thread.start is called and the new thread calls Thread.add, 
     1633        //       the thread will have its stack scanned without first having 
     1634        //       been properly suspended.  Testing has shown this to sometimes 
     1635        //       cause a deadlock. 
     1636         
     1637        while( true ) 
     1638        { 
     1639            synchronized( slock ) 
     1640            { 
     1641                if( !suspendDepth ) 
     1642                { 
     1643                    if( sm_cbeg ) 
     1644                    { 
     1645                        c.next = sm_cbeg; 
     1646                        sm_cbeg.prev = c; 
     1647                    } 
     1648                    sm_cbeg = c; 
     1649                    ++sm_clen; 
     1650                   return; 
     1651                } 
     1652            } 
     1653            yield(); 
    16291654        } 
    16301655    } 
    16311656 
    16321657 
    16331658    // 
    16341659    // Remove a context from the global context list. 
    16351660    // 
    16361661    static void remove( Context* c ) 
    16371662    in 
    16381663    { 
     
    16761701        assert( t.isRunning ); 
    16771702    } 
    16781703    body 
    16791704    { 
    16801705        // NOTE: This loop is necessary to avoid a race between newly created 
    16811706        //       threads and the GC.  If a collection starts between the time 
    16821707        //       Thread.start is called and the new thread calls Thread.add, 
    16831708        //       the thread could manipulate global state while the collection 
    16841709        //       is running, and by being added to the thread list it could be 
    16851710        //       resumed by the GC when it was never suspended, which would 
    1686         //       result in an exception thrown by the GC code.  An alternative 
    1687         //       would be to have Thread.start call Thread.add for the new 
    1688         //       thread, but this introduces its own problems, since the 
    1689         //       thread object isn't entirely ready to be operated on by the 
    1690         //       GC.  This could be fixed by tracking thread startup status, 
    1691         //       but it's far easier to simply have Thread.add wait for any 
    1692         //       running collection to stop before altering the thread list. 
     1711        //       result in an exception thrown by the GC code. 
     1712        //        
     1713        //       An alternative would be to have Thread.start call Thread.add 
     1714        //       for the new thread, but this may introduce its own problems, 
     1715        //       since the thread object isn't entirely ready to be operated 
     1716        //       on by the GC.  This could be fixed by tracking thread startup 
     1717        //       status, but it's far easier to simply have Thread.add wait 
     1718        //       for any running collection to stop before altering the thread 
     1719        //       list. 
     1720        // 
     1721        //       After further testing, having add wait for a collect to end 
     1722        //       proved to have its own problems (explained in Thread.start), 
     1723        //       so add(Thread) is now being done in Thread.start.  This 
     1724        //       reintroduced the deadlock issue mentioned in bugzilla 4890, 
     1725        //       which appears to have been solved by doing this same wait 
     1726        //       procedure in add(Context).  These comments will remain in 
     1727        //       case other issues surface that require the startup state 
     1728        //       tracking described above. 
    16931729         
    16941730        while( true ) 
    16951731        { 
    16961732            synchronized( slock ) 
    16971733            { 
    16981734                if( !suspendDepth ) 
    16991735                { 
    17001736                    if( sm_tbeg ) 
    17011737                    { 
    17021738                        t.next = sm_tbeg;