glib: adding GRunInMain() and GRunIsMain()

This is needed to move one-time operations from AkonadiSyncSource into the
main thread if (and only if) the source is used in a background
thread. Akonadi can't be used in a thread unless the thread was created with
QThread, which is not the case in SyncEvolution.

The basic functionality is the same as the older GRunWhile(). It just has to
be extended so that the action is guaranteed to be called only in the main
thread and there runs only once. This is achieved by turning the action
into a check which immediately returns false.

AkonadiSyncSource cannot include GLibSupport.h due to conflicting usage of
"signal" (a define in Qt, parameter name in glib), therefore these functions
are now made available via util.h.
This commit is contained in:
Patrick Ohly 2014-03-13 05:27:06 -07:00
parent 2a63d6d694
commit 07bc8414c2
3 changed files with 82 additions and 23 deletions

View file

@ -25,6 +25,7 @@
#endif
#include <boost/bind.hpp>
#include <boost/lambda/bind.hpp>
#include <set>
#include <string.h>
@ -224,7 +225,7 @@ public:
* Called by additional threads. Returns when check()
* returned false.
*/
void blockOnCheck(const boost::function<bool ()> &check);
void blockOnCheck(const boost::function<bool ()> &check, bool checkFirst);
};
void PendingChecks::runChecks()
@ -259,22 +260,26 @@ void PendingChecks::runChecks()
}
}
void PendingChecks::blockOnCheck(const boost::function<bool ()> &check)
void PendingChecks::blockOnCheck(const boost::function<bool ()> &check, bool checkFirst)
{
DynMutex::Guard guard = m_mutex.lock();
// When we get here, the conditions for returning may already have
// been met. Check before sleeping. If we need to continue, then
// holding the mutex ensures that the main thread will run the
// check on the next iteration.
if (check()) {
if (!checkFirst || check()) {
m_checks.insert(&check);
if (!checkFirst) {
// Must wake up the main thread from its g_main_context_iteration.
g_main_context_wakeup(g_main_context_default());
}
do {
m_cond.wait(m_mutex);
} while (m_checks.find(&check) != m_checks.end());
}
}
void GRunWhile(const boost::function<bool ()> &check)
void GRunWhile(const boost::function<bool ()> &check, bool checkFirst)
{
static PendingChecks checks;
if (g_main_context_is_owner(g_main_context_default())) {
@ -288,10 +293,41 @@ void GRunWhile(const boost::function<bool ()> &check)
}
} else {
// Transfer check into main thread.
checks.blockOnCheck(check);
checks.blockOnCheck(check, checkFirst);
}
}
static std::string NoThrow(const boost::function<void ()> &action) throw ()
{
try {
action();
} catch (...) {
//
std::string explanation;
Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
return explanation;
}
return "";
}
void GRunInMain(const boost::function<void ()> &action)
{
std::string explanation;
// Wrap in NoThrow, then rethrow exception in current thread if there was a problem.
GRunWhile((boost::lambda::var(explanation) = boost::lambda::bind(NoThrow, action), false), false);
if (!explanation.empty()) {
Exception::tryRethrow(explanation, true);
}
}
bool GRunIsMain()
{
// This works because SyncContext::initMain() permanently acquires
// the main context in the main thread.
return g_main_context_is_owner(g_main_context_default());
}
#ifdef ENABLE_UNIT_TESTS
class GLibTest : public CppUnit::TestFixture {

View file

@ -1042,24 +1042,6 @@ template<> class GAsyncReadyDoneCXX<void>
GRunWhile(! boost::lambda::var(done)); \
} while (false); \
/**
* Process events in the default context while the callback returns
* true.
*
* This must be used instead of g_main_context_iterate() by code which
* may get called in other threads. In that case the check is
* transferred to the main thread which does the actual event
* processing. g_main_context_iterate() would just block because we
* register the main thread as permanent owner of the default context,
* or would suffer from race conditions if we didn't.
*
* The main thread must also be running GRunWhile().
*
* Exceptions in the check code are fatal and should be avoided.
*/
void GRunWhile(const boost::function<bool ()> &check);
#endif // HAVE_GLIB
SE_END_CXX

View file

@ -677,5 +677,46 @@ std::string getCurrentTime();
#define SE_THROW_EXCEPTION_STATUS(_class, _what, _status) \
throw _class(__FILE__, __LINE__, _what, _status)
// GRunWhile(), GRunInMain(), GRunIsMain() depend on glib support,
// which pretty much is a hard requirement of SyncEvolution these days.
// Different implementations would be possible and the APIs do not depend
// on glib types, therefore they are defined here. The glib implemention
// is in GLibSupport.cpp.
/**
* Process events in the default context while the callback returns
* true.
*
* This must be used instead of g_main_context_iterate() by code which
* may get called in other threads. In that case the check is
* transferred to the main thread which does the actual event
* processing. g_main_context_iterate() would just block because we
* register the main thread as permanent owner of the default context,
* or would suffer from race conditions if we didn't.
*
* The main thread must also be running GRunWhile().
*
* Exceptions in the check code are fatal and should be avoided.
*
* The check code will be called in the current thread once if checkFirst
* is true, otherwise it will only be invoked in the main thread. Use that
* latter mode for code which must run in the main thread.
*/
void GRunWhile(const boost::function<bool ()> &check, bool checkFirst = true);
/**
* Runs the action in the main thread once, then returns. Any
* exception thrown by the action will be caught and rethrown in the
* calling thread.
*/
void GRunInMain(const boost::function<void ()> &action);
/**
* True iff the calling thread is handling the main event loop. Can
* be used to avoid GRunInMain().
*/
bool GRunIsMain();
SE_END_CXX
#endif // INCL_SYNCEVOLUTION_UTIL