[mythtv-commits] Ticket #13414: Valgrind error in DVBStreamHandler / DeviceReadBuffer

MythTV noreply at mythtv.org
Tue Feb 26 16:30:32 UTC 2019


#13414: Valgrind error in DVBStreamHandler / DeviceReadBuffer
---------------------------------+-----------------------------------
     Reporter:  Klaas de Waal    |      Owner:  (none)
         Type:  Patch - Bug Fix  |     Status:  new
     Priority:  major            |  Milestone:  needs_triage
    Component:  MythTV - DVB     |    Version:  Master Head
     Severity:  medium           |   Keywords:  DVB segfault valgrind
Ticket locked:  0                |
---------------------------------+-----------------------------------
 Summary:\\
 Testing with valgrind shows sometimes a bug in the DVBStreamHandler about
 access of memory which has already been free'd. This happens at the end of
 a recording. Such an error can cause all kinds of errors, including
 segmentation faults.\\
 A fix for this is attached.\\

 Analysis:\\
 The problem lies in DVBStreamHandler::SetRunningDesired where first the
 m_running_desired flag is set to false and then the DeviceReadBuffer is
 stopped by calling _drb->Stop().

 {{{
 void DVBStreamHandler::SetRunningDesired(bool desired)
 {
     if (_drb && m_running_desired && !desired)
     {
         StreamHandler::SetRunningDesired(desired);
         _drb->Stop();
     }
     else
     {
         StreamHandler::SetRunningDesired(desired);
     }
 }

 }}}

 {{{
 void StreamHandler::SetRunningDesired(bool desired)
 {
     m_running_desired = desired;
     if (!desired)
         MThread::exit(0);
 }

 }}}

 When m_running_desired is false then DVBStreamHandler::RunTS, where the
 DeviceReadBuffer is created, deletes the DeviceReadBuffer after having set
 _drb to nullptr. This is in lines 293-303 of DVBStreamHandler::RunTS:

 {{{
     {
         QMutexLocker locker(&m_start_stop_lock);
         _drb = nullptr;
     }

     if (drb)
     {
         if (drb->IsRunning())
             drb->Stop();
         delete drb;
     }

 }}}

 Depending on the dynamics of a multi-threaded application and on compilers
 saving non-volatile values in registers, the value of _drb in
 DVBStreamHandler::SetRunningDesired() after calling
 StreamHandler::SetRunningDesired() can now be one of the following:
 - point to a valid DeviceReadBuffer
 - point to a free'd DeviceReadBuffer
 - zero/nullptr
 \\
 The pointer is still valid when RunTS has not yet deleted the
 DeviceReadBuffer; in this case everything is OK and there is no valgrind
 error message.\\

 When RunTS has already deleted the DeviceReadBuffer then the pointer
 points to a free'd DeviceReadBuffer and this is what causes
 the valgrind error message. Because the access is really soon after the
 free the memory content is usually still there so most of the time nothing
 problematic happens. When the memory has been re-used then anything can
 happen.\\

 When the pointer is also zero in SetRunningDesired then this causes a
 segmentation fault of the mythbackend.\\

 The hypotheses that there is a race condition between two threads can be
 tested by adding a 50 millisecond delay as follows:

 {{{
 void DVBStreamHandler::SetRunningDesired(bool desired)
 {
     if (_drb && m_running_desired && !desired)
     {
         StreamHandler::SetRunningDesired(desired);
         std::this_thread::sleep_for(std::chrono::milliseconds(50));     //
 KdW this triggers the segfault
         _drb->Stop();
     }
     else
     {
         StreamHandler::SetRunningDesired(desired);
     }
 }

 }}}
 The logic flow has not changed by this small delay but mythbackend now
 crashes reliably at end of the first recording.\\

 The least-intrusive fix for this is to stop the DeviceReadBuffer before it
 is deleted:

 {{{
 void DVBStreamHandler::SetRunningDesired(bool desired)
 {
     if (_drb && m_running_desired && !desired)
     {
         _drb->Stop();
         StreamHandler::SetRunningDesired(desired);
     }
     else
     {
         StreamHandler::SetRunningDesired(desired);
     }
 }

 }}}
 The patch for this is attached as 20190226-dvbsh-drb-v1.patch\\


 Examining the log files shows that the DeviceRequestBlock::Stop() function
 is called three times at the end of each recording:\\
 - First time is in DVBStreamHandler::SetRunningDesired()
 - Second time in DVBStreamHandler::RunTS() before the delete:

 {{{
    if (drb)
     {
         if (drb->IsRunning())
             drb->Stop();
         delete drb;
     }
 }}}
 - Third time in DeviceReadBuffer::~DeviceReadBuffer():

 {{{
 DeviceReadBuffer::~DeviceReadBuffer()
 {
     Stop();
     if (m_buffer)
     {
         delete[] m_buffer;
         m_buffer = nullptr;
     }
 }

 }}}

 Because the Stop() is called unconditionally in the destructor there is no
 need to call it in DVBStreamHandler::RunTS or in
 DVBStreamHandler::SetRunningDesired().\\

 The only thing DVBStreamHandler::SetRunningDesired() then does is call
 StreamHandler::SetRunningDesired(). The conclusion is that the
 DVBStreamHandler::SetRunningDesired() can be removed.\\

 The result, as tested on my system, is that the Stop() is called only once
 and that there are no valgrind messages anymore related to this.\\

 The patch for all this is attached as 20190226-dvbsh-drb-v5.patch.\\

-- 
Ticket URL: <https://code.mythtv.org/trac/ticket/13414>
MythTV <http://www.mythtv.org>
MythTV Media Center


More information about the mythtv-commits mailing list