Skip to content

Commit

Permalink
Make WantingPlayback work when more than one registered client exists…
Browse files Browse the repository at this point in the history
… in the same thread.

Connection to registered playback clients are created using Qt::BlockingQueuedConnection, which will deadlock if running within the same thread.
Add InWantingPlayback methods, to determine if interrupted signal was received while in the process of starting new playback

Part of fix for #11671
  • Loading branch information
jyavenard committed Jul 14, 2013
1 parent 1bc8169 commit 22e5fb0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 1 deletion.
64 changes: 63 additions & 1 deletion mythtv/libs/libmythbase/mythcorecontext.cpp
Expand Up @@ -90,7 +90,8 @@ class MythCoreContextPrivate : public QObject

QMap<QObject *, QByteArray> m_playbackClients;
QMutex m_playbackLock;

bool m_inwanting;

MythPluginManager *m_pluginmanager;
};

Expand Down Expand Up @@ -1397,6 +1398,9 @@ void MythCoreContext::WaitUntilSignals(const char *signal1, ...)
*/
void MythCoreContext::RegisterForPlayback(QObject *sender, const char *method)
{
if (!sender || !method)
return;

QMutexLocker lock(&d->m_playbackLock);

if (!d->m_playbackClients.contains(sender))
Expand Down Expand Up @@ -1438,20 +1442,78 @@ void MythCoreContext::WantingPlayback(QObject *sender)
QMutexLocker lock(&d->m_playbackLock);
QByteArray ba;
const char *method = NULL;
d->m_inwanting = true;

// If any registered client are in the same thread, they will deadlock, so rebuild
// connections for any clients in the same thread as non-blocking connection
QThread *currentThread = QThread::currentThread();

QMap<QObject *, QByteArray>::iterator it = d->m_playbackClients.begin();
for (; it != d->m_playbackClients.end(); ++it)
{
if (it.key() == sender)
continue; // will be done separately, no need to do it again

QThread *thread = it.key()->thread();

if (thread != currentThread)
continue;

disconnect(this, SIGNAL(TVPlaybackAboutToStart()), it.key(), it.value());
connect(this, SIGNAL(TVPlaybackAboutToStart()), it.key(), it.value());
}

// disconnect sender so it won't receive the message
if (d->m_playbackClients.contains(sender))
{
ba = d->m_playbackClients.value(sender);
method = ba.constData();
disconnect(this, SIGNAL(TVPlaybackAboutToStart()), sender, method);
}

// emit signal
emit TVPlaybackAboutToStart();

// reconnect sender
if (method)
{
connect(this, SIGNAL(TVPlaybackAboutToStart()),
sender, method,
Qt::BlockingQueuedConnection);
}
// Restore blocking connections
for (; it != d->m_playbackClients.end(); ++it)
{
if (it.key() == sender)
continue; // already done above, no need to do it again

QThread *thread = it.key()->thread();

if (thread != currentThread)
continue;

disconnect(this, SIGNAL(TVPlaybackAboutToStart()), it.key(), it.value());
connect(this, SIGNAL(TVPlaybackAboutToStart()),
it.key(), it.value(), Qt::BlockingQueuedConnection);
}
d->m_inwanting = false;
}

/**
* Returns true if a client has requested playback.
* this can be used when one of the TVPlayback* is emitted to find out if you
* can assume playback has stopped
*/
bool MythCoreContext::InWantingPlayback(void)
{
bool locked = d->m_playbackLock.tryLock();

if (!locked && d->m_inwanting)
return true; // we're in the middle of WantingPlayback
if (!locked)
return false;
d->m_playbackLock.unlock();
return false;
}

bool MythCoreContext::TestPluginVersion(const QString &name,
Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmythbase/mythcorecontext.h
Expand Up @@ -176,6 +176,7 @@ class MBASE_PUBLIC MythCoreContext : public QObject, public MythObservable, publ
void RegisterForPlayback(QObject *sender, const char *method);
void UnregisterForPlayback(QObject *sender);
void WantingPlayback(QObject *sender);
bool InWantingPlayback(void);

// Plugin related methods
bool TestPluginVersion(const QString &name, const QString &libversion,
Expand Down

0 comments on commit 22e5fb0

Please sign in to comment.