416 lines
11 KiB
C++
416 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2011 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <syncevo/GLibSupport.h>
|
|
#include <syncevo/Exception.h>
|
|
#include <syncevo/SmartPtr.h>
|
|
#ifdef ENABLE_UNIT_TESTS
|
|
#include "test.h"
|
|
#endif
|
|
|
|
#include <boost/bind.hpp>
|
|
#include <boost/lambda/bind.hpp>
|
|
#include <set>
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_GLIB
|
|
#include <glib-object.h>
|
|
#include <glib.h>
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
SE_BEGIN_CXX
|
|
|
|
#ifdef HAVE_GLIB
|
|
|
|
class Select {
|
|
GMainLoop *m_loop;
|
|
GMainContext *m_context;
|
|
struct FDSource;
|
|
FDSource *m_source;
|
|
Timespec m_deadline;
|
|
GPollFD m_pollfd;
|
|
GLibSelectResult m_result;
|
|
|
|
struct FDSource
|
|
{
|
|
GSource m_source;
|
|
Select *m_select;
|
|
|
|
static gboolean prepare(GSource *source,
|
|
gint *timeout)
|
|
{
|
|
FDSource *fdsource = (FDSource *)source;
|
|
if (!fdsource->m_select->m_deadline) {
|
|
*timeout = -1;
|
|
return FALSE;
|
|
}
|
|
|
|
Timespec now = Timespec::monotonic();
|
|
if (now < fdsource->m_select->m_deadline) {
|
|
Timespec delta = fdsource->m_select->m_deadline - now;
|
|
*timeout = delta.tv_sec * 1000 + delta.tv_nsec / 1000000;
|
|
return FALSE;
|
|
} else {
|
|
fdsource->m_select->m_result = GLIB_SELECT_TIMEOUT;
|
|
*timeout = 0;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean check(GSource *source)
|
|
{
|
|
FDSource *fdsource = (FDSource *)source;
|
|
if (fdsource->m_select->m_pollfd.revents) {
|
|
fdsource->m_select->m_result = GLIB_SELECT_READY;
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean dispatch(GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
FDSource *fdsource = (FDSource *)source;
|
|
g_main_loop_quit(fdsource->m_select->m_loop);
|
|
return FALSE;
|
|
}
|
|
|
|
static GSourceFuncs m_funcs;
|
|
};
|
|
|
|
public:
|
|
Select(GMainLoop *loop, int fd, int direction, Timespec *timeout) :
|
|
m_loop(loop),
|
|
m_context(g_main_loop_get_context(loop)),
|
|
m_result(GLIB_SELECT_QUIT)
|
|
{
|
|
if (timeout) {
|
|
m_deadline = Timespec::monotonic() + *timeout;
|
|
}
|
|
|
|
memset(&m_pollfd, 0, sizeof(m_pollfd));
|
|
m_source = (FDSource *)g_source_new(&FDSource::m_funcs, sizeof(FDSource));
|
|
if (!m_source) {
|
|
SE_THROW("no FDSource");
|
|
}
|
|
m_source->m_select = this;
|
|
m_pollfd.fd = fd;
|
|
if (fd >= 0 &&
|
|
direction != GLIB_SELECT_NONE) {
|
|
if (direction & GLIB_SELECT_READ) {
|
|
m_pollfd.events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
|
|
}
|
|
if (direction & GLIB_SELECT_WRITE) {
|
|
m_pollfd.events |= G_IO_OUT | G_IO_ERR;
|
|
}
|
|
g_source_add_poll(&m_source->m_source, &m_pollfd);
|
|
}
|
|
g_source_attach(&m_source->m_source, m_context);
|
|
}
|
|
|
|
~Select()
|
|
{
|
|
if (m_source) {
|
|
g_source_destroy(&m_source->m_source);
|
|
}
|
|
}
|
|
|
|
GLibSelectResult run()
|
|
{
|
|
g_main_loop_run(m_loop);
|
|
return m_result;
|
|
}
|
|
};
|
|
|
|
GSourceFuncs Select::FDSource::m_funcs = {
|
|
Select::FDSource::prepare,
|
|
Select::FDSource::check,
|
|
Select::FDSource::dispatch
|
|
};
|
|
|
|
GLibSelectResult GLibSelect(GMainLoop *loop, int fd, int direction, Timespec *timeout)
|
|
{
|
|
Select instance(loop, fd, direction, timeout);
|
|
return instance.run();
|
|
}
|
|
|
|
void GErrorCXX::throwError(const SourceLocation &where, const string &action)
|
|
{
|
|
throwError(where, action, m_gerror);
|
|
}
|
|
|
|
void GErrorCXX::throwError(const SourceLocation &where, const string &action, const GError *err)
|
|
{
|
|
string gerrorstr = action;
|
|
if (!gerrorstr.empty()) {
|
|
gerrorstr += ": ";
|
|
}
|
|
if (err) {
|
|
gerrorstr += err->message;
|
|
// No need to clear m_error! Will be done as part of
|
|
// destructing the GErrorCCXX.
|
|
} else {
|
|
gerrorstr = "failure";
|
|
}
|
|
|
|
throw Exception(where.m_file, where.m_line, gerrorstr);
|
|
}
|
|
|
|
static void changed(GFileMonitor *monitor,
|
|
GFile *file1,
|
|
GFile *file2,
|
|
GFileMonitorEvent event,
|
|
gpointer userdata)
|
|
{
|
|
GLibNotify::callback_t *callback = static_cast<GLibNotify::callback_t *>(userdata);
|
|
if (!callback->empty()) {
|
|
(*callback)(file1, file2, event);
|
|
}
|
|
}
|
|
|
|
GLibNotify::GLibNotify(const char *file,
|
|
const callback_t &callback) :
|
|
m_callback(callback)
|
|
{
|
|
GFileCXX filecxx(g_file_new_for_path(file), TRANSFER_REF);
|
|
GErrorCXX gerror;
|
|
GFileMonitorCXX monitor(g_file_monitor_file(filecxx.get(), G_FILE_MONITOR_NONE, NULL, gerror), TRANSFER_REF);
|
|
m_monitor.swap(monitor);
|
|
if (!m_monitor) {
|
|
gerror.throwError(SE_HERE, std::string("monitoring ") + file);
|
|
}
|
|
g_signal_connect_after(m_monitor.get(),
|
|
"changed",
|
|
G_CALLBACK(changed),
|
|
(void *)&m_callback);
|
|
}
|
|
|
|
class PendingChecks
|
|
{
|
|
typedef std::set<const boost::function<bool ()> *> Checks;
|
|
Checks m_checks;
|
|
DynMutex m_mutex;
|
|
Cond m_cond;
|
|
|
|
public:
|
|
/**
|
|
* Called by main thread before and after sleeping.
|
|
* Runs all registered checks and removes the ones
|
|
* which are done.
|
|
*/
|
|
void runChecks();
|
|
|
|
/**
|
|
* Called by additional threads. Returns when check()
|
|
* returned false.
|
|
*/
|
|
void blockOnCheck(const boost::function<bool ()> &check, bool checkFirst);
|
|
};
|
|
|
|
void PendingChecks::runChecks()
|
|
{
|
|
DynMutex::Guard guard = m_mutex.lock();
|
|
Checks::iterator it = m_checks.begin();
|
|
bool removed = false;
|
|
while (it != m_checks.end()) {
|
|
bool cont;
|
|
try {
|
|
cont = (**it)();
|
|
} catch (...) {
|
|
Exception::handle(HANDLE_EXCEPTION_FATAL);
|
|
// keep compiler happy
|
|
cont = false;
|
|
}
|
|
|
|
if (!cont) {
|
|
// Done with this check
|
|
Checks::iterator next = it;
|
|
++next;
|
|
m_checks.erase(it);
|
|
it = next;
|
|
removed = true;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
// Tell blockOnCheck() calls that they may have completed.
|
|
if (removed) {
|
|
m_cond.signal();
|
|
}
|
|
}
|
|
|
|
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 (!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, bool checkFirst)
|
|
{
|
|
static PendingChecks checks;
|
|
if (g_main_context_is_owner(g_main_context_default())) {
|
|
// Check once before sleeping, conditions may already be met
|
|
// for some checks.
|
|
checks.runChecks();
|
|
// Drive event loop.
|
|
while (check()) {
|
|
g_main_context_iteration(NULL, true);
|
|
checks.runChecks();
|
|
}
|
|
} else {
|
|
// Transfer check into main thread.
|
|
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 {
|
|
CPPUNIT_TEST_SUITE(GLibTest);
|
|
CPPUNIT_TEST(notify);
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
struct Event {
|
|
GFileCXX m_file1;
|
|
GFileCXX m_file2;
|
|
GFileMonitorEvent m_event;
|
|
};
|
|
|
|
static void notifyCallback(list<Event> &events,
|
|
GFile *file1,
|
|
GFile *file2,
|
|
GFileMonitorEvent event)
|
|
{
|
|
Event tmp;
|
|
tmp.m_file1.reset(file1);
|
|
tmp.m_file2.reset(file2);
|
|
tmp.m_event = event;
|
|
events.push_back(tmp);
|
|
}
|
|
|
|
static gboolean timeout(gpointer data)
|
|
{
|
|
g_main_loop_quit(static_cast<GMainLoop *>(data));
|
|
return false;
|
|
}
|
|
|
|
void notify()
|
|
{
|
|
list<Event> events;
|
|
static const char *name = "GLibTest.out";
|
|
unlink(name);
|
|
GMainLoopCXX loop(g_main_loop_new(NULL, FALSE), TRANSFER_REF);
|
|
if (!loop) {
|
|
SE_THROW("could not allocate main loop");
|
|
}
|
|
GLibNotify notify(name, boost::bind(notifyCallback, boost::ref(events), _1, _2, _3));
|
|
{
|
|
events.clear();
|
|
GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout");
|
|
ofstream out(name);
|
|
out << "hello";
|
|
out.close();
|
|
g_main_loop_run(loop.get());
|
|
CPPUNIT_ASSERT(!events.empty());
|
|
}
|
|
|
|
{
|
|
events.clear();
|
|
ofstream out(name);
|
|
out.close();
|
|
GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout");
|
|
g_main_loop_run(loop.get());
|
|
CPPUNIT_ASSERT(!events.empty());
|
|
}
|
|
|
|
{
|
|
events.clear();
|
|
unlink(name);
|
|
GLibEvent id(g_timeout_add_seconds(5, timeout, loop.get()), "timeout");
|
|
g_main_loop_run(loop.get());
|
|
CPPUNIT_ASSERT(!events.empty());
|
|
}
|
|
}
|
|
};
|
|
|
|
SYNCEVOLUTION_TEST_SUITE_REGISTRATION(GLibTest);
|
|
|
|
#endif // ENABLE_UNIT_TESTS
|
|
|
|
#else // HAVE_GLIB
|
|
|
|
GLibSelectResult GLibSelect(GMainLoop *loop, int fd, int direction, Timespec *timeout)
|
|
{
|
|
SE_THROW("GLibSelect() not implemented without glib support");
|
|
return GLIB_SELECT_QUIT;
|
|
}
|
|
|
|
#endif // HAVE_GLIB
|
|
|
|
SE_END_CXX
|