Kaydet (Commit) ede10d79 authored tarafından Michael Stahl's avatar Michael Stahl Kaydeden (comit) Henry Castro

vcl: fix hangs in SvpSalInstance

Since commit cb8bfa9a the main thread
will read from the timer pipe until it is empty. But evidently this
introduces the problem that the poll() in another thread will not
return, as the file descriptor will no longer be readable; see
https://paste.debian.net/1011306/ for a reproducer of that rather
under-documented poll behaviour.  So other threads can get stuck
forever in poll, and then the main thread can block in poll too with
no other thread to wake it up.  This is the problem that plagues
UITest_writerperfect_epubexport.

The timer pipe is difficult to fix, since the main thread can block on
either the poll or the subsequent AcquireYieldMutex().

So replace the timer pipe with a condition etc. that is mostly
copied from the OSX AquaSalInstance/SalYieldMutex implementation.

The main thread now does something different than the other threads,
and blocks on a condition_variable with a timeout, while other
threads still block on acquiring the mutex.

Non-main threads can poke the main thread to do a DoYield()
on their behalf, and then get the result back with a blocking
read from a pipe, all while holding the YieldMutex.  This
requires some fudging of the YieldMutex so that the main
thread can borrow the ownership temporarily.

Unfortunately SvpSalInstance, in addition to being directly
instantiable for --headless, has a whole bunch of subclasses:

* HeadlessSalInstance
* AndroidSalInstance
* IosSalInstance
* GtkInstance (in the gtk3 case)
* KDE5SalInstance

Of these GtkInstance overrides everything related to the
DoYield/SalYieldMutex implementation, but the others will be
affected by the change.

This commit will probably break IOS due to me not understanding the
point of the undocumented random #ifdef IOS in svpinst.cxx.

Change-Id: I1bbb143952dda89579e97ac32cd147e5b987573c
Reviewed-on: https://gerrit.libreoffice.org/50237Tested-by: 's avatarJenkins <ci@libreoffice.org>
Reviewed-by: 's avatarMichael Stahl <mstahl@redhat.com>
üst 3b388ea5
...@@ -189,7 +189,7 @@ SalData::~SalData() ...@@ -189,7 +189,7 @@ SalData::~SalData()
SalInstance *CreateSalInstance() SalInstance *CreateSalInstance()
{ {
LOGI("Android: CreateSalInstance!"); LOGI("Android: CreateSalInstance!");
AndroidSalInstance* pInstance = new AndroidSalInstance( new SalYieldMutex() ); AndroidSalInstance* pInstance = new AndroidSalInstance( new SvpSalYieldMutex() );
new AndroidSalData( pInstance ); new AndroidSalData( pInstance );
pInstance->AcquireYieldMutex(); pInstance->AcquireYieldMutex();
return pInstance; return pInstance;
......
...@@ -90,7 +90,7 @@ SalData::~SalData() ...@@ -90,7 +90,7 @@ SalData::~SalData()
// This is our main entry point: // This is our main entry point:
SalInstance *CreateSalInstance() SalInstance *CreateSalInstance()
{ {
HeadlessSalInstance* pInstance = new HeadlessSalInstance( new SalYieldMutex() ); HeadlessSalInstance* pInstance = new HeadlessSalInstance( new SvpSalYieldMutex() );
new HeadlessSalData( pInstance ); new HeadlessSalData( pInstance );
pInstance->AcquireYieldMutex(); pInstance->AcquireYieldMutex();
return pInstance; return pInstance;
......
...@@ -21,7 +21,7 @@ public: ...@@ -21,7 +21,7 @@ public:
// plugin factory function // plugin factory function
SalInstance* svp_create_SalInstance() SalInstance* svp_create_SalInstance()
{ {
SvpSalInstance* pInstance = new SvpSalInstance( new SalYieldMutex() ); SvpSalInstance* pInstance = new SvpSalInstance( new SvpSalYieldMutex() );
new SvpSalData( pInstance ); new SvpSalData( pInstance );
return pInstance; return pInstance;
} }
......
...@@ -62,15 +62,15 @@ static void atfork_child() ...@@ -62,15 +62,15 @@ static void atfork_child()
#endif #endif
SvpSalInstance::SvpSalInstance( SalYieldMutex *pMutex ) : SvpSalInstance::SvpSalInstance( SalYieldMutex *pMutex )
SalGenericInstance( pMutex ) : SalGenericInstance( pMutex )
{ {
m_aTimeout.tv_sec = 0; m_aTimeout.tv_sec = 0;
m_aTimeout.tv_usec = 0; m_aTimeout.tv_usec = 0;
m_nTimeoutMS = 0; m_nTimeoutMS = 0;
#ifndef IOS #ifndef IOS
m_pTimeoutFDS[0] = m_pTimeoutFDS[1] = -1; m_MainThread = osl::Thread::getCurrentIdentifier();
CreateWakeupPipe(true); CreateWakeupPipe(true);
#endif #endif
if( s_pDefaultInstance == nullptr ) if( s_pDefaultInstance == nullptr )
...@@ -93,60 +93,56 @@ SvpSalInstance::~SvpSalInstance() ...@@ -93,60 +93,56 @@ SvpSalInstance::~SvpSalInstance()
void SvpSalInstance::CloseWakeupPipe(bool log) void SvpSalInstance::CloseWakeupPipe(bool log)
{ {
if (m_pTimeoutFDS[0] != -1) SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()));
if (!pMutex)
return;
if (pMutex->m_FeedbackFDs[0] != -1)
{ {
if (log) if (log)
{ {
SAL_INFO("vcl.headless", "CloseWakeupPipe: Closing inherited wakeup pipe: [" << m_pTimeoutFDS[0] << "," << m_pTimeoutFDS[1] << "]"); SAL_INFO("vcl.headless", "CloseWakeupPipe: Closing inherited feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]");
} }
close (m_pTimeoutFDS[0]); close (pMutex->m_FeedbackFDs[0]);
close (m_pTimeoutFDS[1]); close (pMutex->m_FeedbackFDs[1]);
m_pTimeoutFDS[0] = m_pTimeoutFDS[1] = -1; pMutex->m_FeedbackFDs[0] = pMutex->m_FeedbackFDs[1] = -1;
} }
} }
void SvpSalInstance::CreateWakeupPipe(bool log) void SvpSalInstance::CreateWakeupPipe(bool log)
{ {
if (pipe (m_pTimeoutFDS) == -1) SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()));
if (!pMutex)
return;
if (pipe (pMutex->m_FeedbackFDs) == -1)
{ {
if (log) if (log)
{ {
SAL_WARN("vcl.headless", "Could not create wakeup pipe: " << strerror(errno)); SAL_WARN("vcl.headless", "Could not create feedback pipe: " << strerror(errno));
std::abort();
} }
} }
else else
{ {
if (log) if (log)
{ {
SAL_INFO("vcl.headless", "CreateWakeupPipe: Created wakeup pipe: [" << m_pTimeoutFDS[0] << "," << m_pTimeoutFDS[1] << "]"); SAL_INFO("vcl.headless", "CreateWakeupPipe: Created feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]");
} }
// initialize 'wakeup' pipe.
int flags; int flags;
// set close-on-exec descriptor flag. // set close-on-exec descriptor flag.
if ((flags = fcntl (m_pTimeoutFDS[0], F_GETFD)) != -1) if ((flags = fcntl (pMutex->m_FeedbackFDs[0], F_GETFD)) != -1)
{ {
flags |= FD_CLOEXEC; flags |= FD_CLOEXEC;
(void)fcntl(m_pTimeoutFDS[0], F_SETFD, flags); (void) fcntl(pMutex->m_FeedbackFDs[0], F_SETFD, flags);
} }
if ((flags = fcntl (m_pTimeoutFDS[1], F_GETFD)) != -1) if ((flags = fcntl (pMutex->m_FeedbackFDs[1], F_GETFD)) != -1)
{ {
flags |= FD_CLOEXEC; flags |= FD_CLOEXEC;
(void)fcntl(m_pTimeoutFDS[1], F_SETFD, flags); (void) fcntl(pMutex->m_FeedbackFDs[1], F_SETFD, flags);
} }
// set non-blocking I/O flag. // retain the default blocking I/O for feedback pipe
if ((flags = fcntl(m_pTimeoutFDS[0], F_GETFL)) != -1)
{
flags |= O_NONBLOCK;
(void)fcntl(m_pTimeoutFDS[0], F_SETFL, flags);
}
if ((flags = fcntl(m_pTimeoutFDS[1], F_GETFL)) != -1)
{
flags |= O_NONBLOCK;
(void)fcntl(m_pTimeoutFDS[1], F_SETFL, flags);
}
} }
} }
...@@ -157,10 +153,29 @@ void SvpSalInstance::TriggerUserEventProcessing() ...@@ -157,10 +153,29 @@ void SvpSalInstance::TriggerUserEventProcessing()
Wakeup(); Wakeup();
} }
void SvpSalInstance::Wakeup() #ifndef NDEBUG
static bool g_CheckedMutex = false;
#endif
void SvpSalInstance::Wakeup(SvpRequest const request)
{ {
#ifndef NDEBUG
if (!g_CheckedMutex)
{
assert(dynamic_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()) != nullptr
&& "This SvpSalInstance function requires use of SvpSalYieldMutex");
g_CheckedMutex = true;
}
#endif
#ifndef IOS #ifndef IOS
OSL_VERIFY(write (m_pTimeoutFDS[1], "", 1) == 1); SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()));
std::unique_lock<std::mutex> g(pMutex->m_WakeUpMainMutex);
if (request != SvpRequest::NONE)
{
pMutex->m_Request = request;
}
pMutex->m_wakeUpMain = true;
pMutex->m_WakeUpMainCond.notify_one();
#endif #endif
} }
...@@ -259,72 +274,199 @@ void SvpSalInstance::ProcessEvent( SalUserEvent aEvent ) ...@@ -259,72 +274,199 @@ void SvpSalInstance::ProcessEvent( SalUserEvent aEvent )
const SvpSalFrame* pSvpFrame = static_cast<const SvpSalFrame*>( aEvent.m_pFrame); const SvpSalFrame* pSvpFrame = static_cast<const SvpSalFrame*>( aEvent.m_pFrame);
pSvpFrame->PostPaint(); pSvpFrame->PostPaint();
} }
#ifndef NDEBUG
if (!g_CheckedMutex)
{
assert(dynamic_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()) != nullptr
&& "This SvpSalInstance function requires use of SvpSalYieldMutex");
g_CheckedMutex = true;
}
#endif
SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()));
pMutex->m_NonMainWaitingYieldCond.set();
} }
SvpSalYieldMutex::SvpSalYieldMutex()
bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{ {
// first, process current user events m_FeedbackFDs[0] = m_FeedbackFDs[1] = -1;
bool bEvent = DispatchUserEvents( bHandleAllCurrentEvents ); }
if ( !bHandleAllCurrentEvents && bEvent )
return true;
bEvent = CheckTimeout() || bEvent; SvpSalYieldMutex::~SvpSalYieldMutex()
{
}
if (bWait && ! bEvent ) void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount)
{
SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance);
if (pInst && pInst->IsMainThread())
{ {
int nTimeoutMS = 0; if (m_bNoYieldLock)
if (m_aTimeout.tv_sec) // Timer is started. return;
do
{ {
timeval Timeout; SvpRequest request = SvpRequest::NONE;
// determine remaining timeout. {
gettimeofday (&Timeout, nullptr); std::unique_lock<std::mutex> g(m_WakeUpMainMutex);
if ( m_aTimeout > Timeout ) if (m_aMutex.tryToAcquire()) {
// if there's a request, the other thread holds m_aMutex
assert(m_Request == SvpRequest::NONE);
m_wakeUpMain = false;
break;
}
m_WakeUpMainCond.wait(g, [this]() { return m_wakeUpMain; });
m_wakeUpMain = false;
std::swap(m_Request, request);
}
if (request != SvpRequest::NONE)
{ {
int nTimeoutMicroS = m_aTimeout.tv_usec - Timeout.tv_usec; // nested Yield on behalf of another thread
nTimeoutMS = (m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 assert(!m_bNoYieldLock);
+ nTimeoutMicroS / 1000; m_bNoYieldLock = true;
if ( nTimeoutMicroS % 1000 ) bool const bEvents = pInst->DoYield(false, request == SvpRequest::MainThreadDispatchAllEvents);
nTimeoutMS += 1; m_bNoYieldLock = false;
write(m_FeedbackFDs[1], &bEvents, sizeof(bool));
} }
} }
while (true);
}
else
{
m_aMutex.acquire();
}
++m_nCount;
SalYieldMutex::doAcquire(nLockCount - 1);
}
sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll)
{
SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance);
if (pInst && pInst->IsMainThread())
{
if (m_bNoYieldLock)
return 1;
else else
nTimeoutMS = -1; // wait until something happens return SalYieldMutex::doRelease(bUnlockAll);
}
sal_uInt32 nCount;
{
// read m_nCount before doRelease
bool const isReleased(bUnlockAll || m_nCount == 1);
nCount = comphelper::GenericSolarMutex::doRelease( bUnlockAll );
if (isReleased) {
std::unique_lock<std::mutex> g(m_WakeUpMainMutex);
m_wakeUpMain = true;
m_WakeUpMainCond.notify_one();
}
}
return nCount;
}
DoReleaseYield(nTimeoutMS); bool SvpSalYieldMutex::IsCurrentThread() const
{
if (GetSalData()->m_pInstance->IsMainThread() && m_bNoYieldLock)
{
return true;
} }
else if ( bEvent ) else
{ {
// Drain the wakeup pipe return SalYieldMutex::IsCurrentThread();
int buffer;
while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0);
} }
}
return bEvent; bool SvpSalInstance::IsMainThread() const
{
return osl::Thread::getCurrentIdentifier() == m_MainThread;
} }
void SvpSalInstance::DoReleaseYield( int nTimeoutMS ) bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
{ {
// poll #ifndef NDEBUG
struct pollfd aPoll; if (!g_CheckedMutex)
aPoll.fd = m_pTimeoutFDS[0]; {
aPoll.events = POLLIN; assert(dynamic_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()) != nullptr
aPoll.revents = 0; && "This SvpSalInstance function requires use of SvpSalYieldMutex");
g_CheckedMutex = true;
}
#endif
// release yield mutex // first, process current user events
sal_uInt32 nAcquireCount = ReleaseYieldMutexAll(); bool bEvent = DispatchUserEvents( bHandleAllCurrentEvents );
if ( !bHandleAllCurrentEvents && bEvent )
return true;
(void)poll( &aPoll, 1, nTimeoutMS ); bEvent = CheckTimeout() || bEvent;
// acquire yield mutex again SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(mpSalYieldMutex.get()));
AcquireYieldMutex( nAcquireCount );
// clean up pipe if (IsMainThread())
if( (aPoll.revents & POLLIN) != 0 ) {
if (bWait && ! bEvent)
{
int nTimeoutMS = 0;
if (m_aTimeout.tv_sec) // Timer is started.
{
timeval Timeout;
// determine remaining timeout.
gettimeofday (&Timeout, nullptr);
if (m_aTimeout > Timeout)
{
int nTimeoutMicroS = m_aTimeout.tv_usec - Timeout.tv_usec;
nTimeoutMS = (m_aTimeout.tv_sec - Timeout.tv_sec) * 1000
+ nTimeoutMicroS / 1000;
if ( nTimeoutMicroS % 1000 )
nTimeoutMS += 1;
}
}
else
nTimeoutMS = -1; // wait until something happens
sal_uInt32 nAcquireCount = ReleaseYieldMutexAll();
{
std::unique_lock<std::mutex> g(pMutex->m_WakeUpMainMutex);
// wait for doRelease() or Wakeup() to set the condition
if (nTimeoutMS == -1)
{
pMutex->m_WakeUpMainCond.wait(g,
[pMutex]() { return pMutex->m_wakeUpMain; });
}
else
{
pMutex->m_WakeUpMainCond.wait_for(g,
std::chrono::milliseconds(nTimeoutMS),
[pMutex]() { return pMutex->m_wakeUpMain; });
}
// here no need to check m_Request because Acquire will do it
}
AcquireYieldMutex( nAcquireCount );
}
else if (bEvent)
{
pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads
}
}
else // !IsMainThread()
{ {
int buffer; Wakeup(bHandleAllCurrentEvents
while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0); ? SvpRequest::MainThreadDispatchAllEvents
: SvpRequest::MainThreadDispatchOneEvent);
bool bDidWork(false);
// blocking read (for synchronisation)
auto const nRet = read(pMutex->m_FeedbackFDs[0], &bDidWork, sizeof(bool));
assert(nRet == 1); (void) nRet;
if (!bDidWork && bWait)
{
// block & release YieldMutex until the main thread does something
pMutex->m_NonMainWaitingYieldCond.reset();
sal_uInt32 nAcquireCount = ReleaseYieldMutexAll();
pMutex->m_NonMainWaitingYieldCond.wait();
AcquireYieldMutex( nAcquireCount );
}
} }
return bEvent;
} }
bool SvpSalInstance::AnyInput( VclInputFlags nType ) bool SvpSalInstance::AnyInput( VclInputFlags nType )
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <osl/mutex.hxx> #include <osl/mutex.hxx>
#include <osl/thread.hxx> #include <osl/thread.hxx>
#include <osl/conditn.hxx>
#include <salinst.hxx> #include <salinst.hxx>
#include <salwtype.hxx> #include <salwtype.hxx>
#include <saltimer.hxx> #include <saltimer.hxx>
...@@ -30,6 +31,7 @@ ...@@ -30,6 +31,7 @@
#include <unx/genprn.h> #include <unx/genprn.h>
#include <list> #include <list>
#include <condition_variable>
#include <time.h> #include <time.h>
...@@ -56,19 +58,55 @@ public: ...@@ -56,19 +58,55 @@ public:
class SvpSalFrame; class SvpSalFrame;
class GenPspGraphics; class GenPspGraphics;
enum class SvpRequest
{
NONE,
MainThreadDispatchOneEvent,
MainThreadDispatchAllEvents,
};
class SvpSalYieldMutex : public SalYieldMutex
{
private:
// note: these members might as well live in SvpSalInstance, but there is
// at least one subclass of SvpSalInstance (GTK3) that doesn't use them.
friend class SvpSalInstance;
// members for communication from main thread to non-main thread
int m_FeedbackFDs[2];
osl::Condition m_NonMainWaitingYieldCond;
// members for communication from non-main thread to main thread
bool m_bNoYieldLock = false; // accessed only on main thread
std::mutex m_WakeUpMainMutex; // guard m_wakeUpMain & m_Request
std::condition_variable m_WakeUpMainCond;
bool m_wakeUpMain = false;
SvpRequest m_Request = SvpRequest::NONE;
protected:
virtual void doAcquire( sal_uInt32 nLockCount ) override;
virtual sal_uInt32 doRelease( bool bUnlockAll ) override;
public:
SvpSalYieldMutex();
virtual ~SvpSalYieldMutex() override;
virtual bool IsCurrentThread() const override;
};
SalInstance* svp_create_SalInstance(); SalInstance* svp_create_SalInstance();
// NOTE: the functions IsMainThread, DoYield and Wakeup *require* the use of
// SvpSalYieldMutex; if a subclass uses something else it must override these
// (Wakeup is only called by SvpSalTimer and SvpSalFrame)
class VCL_DLLPUBLIC SvpSalInstance : public SalGenericInstance, public SalUserEventList class VCL_DLLPUBLIC SvpSalInstance : public SalGenericInstance, public SalUserEventList
{ {
timeval m_aTimeout; timeval m_aTimeout;
sal_uLong m_nTimeoutMS; sal_uLong m_nTimeoutMS;
int m_pTimeoutFDS[2]; oslThreadIdentifier m_MainThread;
void DoReleaseYield( int nTimeoutMS );
virtual void TriggerUserEventProcessing() override; virtual void TriggerUserEventProcessing() override;
virtual void ProcessEvent( SalUserEvent aEvent ) override; virtual void ProcessEvent( SalUserEvent aEvent ) override;
void Wakeup(); void Wakeup(SvpRequest request = SvpRequest::NONE);
public: public:
static SvpSalInstance* s_pDefaultInstance; static SvpSalInstance* s_pDefaultInstance;
...@@ -132,7 +170,7 @@ public: ...@@ -132,7 +170,7 @@ public:
// and timer // and timer
virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override; virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents) override;
virtual bool AnyInput( VclInputFlags nType ) override; virtual bool AnyInput( VclInputFlags nType ) override;
virtual bool IsMainThread() const override { return true; } virtual bool IsMainThread() const override;
// may return NULL to disable session management // may return NULL to disable session management
virtual SalSession* CreateSalSession() override; virtual SalSession* CreateSalSession() override;
......
...@@ -170,7 +170,7 @@ SalData::~SalData() ...@@ -170,7 +170,7 @@ SalData::~SalData()
// This is our main entry point: // This is our main entry point:
SalInstance *CreateSalInstance() SalInstance *CreateSalInstance()
{ {
IosSalInstance* pInstance = new IosSalInstance( new SalYieldMutex() ); IosSalInstance* pInstance = new IosSalInstance( new SvpSalYieldMutex() );
new IosSalData( pInstance ); new IosSalData( pInstance );
pInstance->AcquireYieldMutex(); pInstance->AcquireYieldMutex();
return pInstance; return pInstance;
......
...@@ -72,7 +72,7 @@ extern "C" { ...@@ -72,7 +72,7 @@ extern "C" {
} }
#endif #endif
KDE5SalInstance* pInstance = new KDE5SalInstance( new SalYieldMutex() ); KDE5SalInstance* pInstance = new KDE5SalInstance( new SvpSalYieldMutex() );
SAL_INFO( "vcl.kde5", "created KDE5SalInstance " << &pInstance ); SAL_INFO( "vcl.kde5", "created KDE5SalInstance " << &pInstance );
// initialize SalData // initialize SalData
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment