On Mo, 2011-12-19 at 10:57 +0100, Chris Kühl wrote:
> On Fri, Dec 16, 2011 at 3:41 PM, Patrick Ohly <[email protected]> wrote:
> > I have that same example working based on GDBus + libdbus. What I don't
> > like about it is that the connection is based on listen() + connect().
> > This makes it harder for the parent to detect that the child died: if
> > the parent did the usual trick (create socket pair, fork+exec child,
> > close its own side of the socket pair), then a closed socket would
> > indicate that the child died. With listen() + connect() it has to check
> > for existence of the child periodically. SIGCHILD does not integrate
> > into the main loop well, but I see that g_spawn() has a GChildWatch -
> > that should do.
> >
>
> Yes, looking at the source for the GChildWatch related code it blocks
> SIGCHLD. When the mainloop's worker routine is run it checks if any of
> the blocked signals are pending and then iterates over the
> GChildWatchSources checking if the process has exited. So by checking
> the return value of g_spawn_async_with_pipes and setting up the
> GChildWatch I don't see a way to miss the childs exit, either.
Attached is a first API proposal for the fork/exec helper class and how
it could be used in a small test program. Does that look sane?
I haven't started implementing any of the new methods, but it should be
fairly straightforward (famous last words).
> > While working on this I got unhappy with some aspects of the current
> > D-Bus C++ wrapper. The result is the "dbus-cleanup" branch. Chris, what
> > do you think about that?
>
> This looks fine. Moving the connection info to DBusObject definitely
> makes sense and reduces the code a bit.
Okay, merged into master.
--
Best Regards, Patrick Ohly
The content of this message is my personal opinion only and although
I am an employee of Intel, the statements I make here in no way
represent Intel's position on the issue, nor am I authorized to speak
on behalf of Intel on this matter.
/*
* 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
*/
#ifndef INCL_FORK_EXEC
# define INCL_FORK_EXEC
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_GLIB
#include <syncevo/util.h>
#include <syncevo/GLibSupport.h>
#include <syncevo/SmartPtr.h>
#include <boost/signals2.hpp>
SE_BEGIN_CXX
/**
* Utility class which starts a specific helper binary in a second
* process. The helper binary is identified via its base name like
* "syncevo-dbus-helper", exact location is then determined
* automatically, or via an absolute path.
*
* Direct D-Bus communication is set up automatically. For this to
* work, the helper must use ForkExecChild::connect().
*
* Progress (like "client connected") and failures ("client disconnected")
* are reported via boost::signal2 signals. To make progess, the user of
* this class must run a glib event loop.
*
* Note that failures encountered inside the class methods themselves
* will be reported via exceptions. Only asynchronous errors encountered
* inside the event loop are reported via the failure signal.
*/
class ForkExec : private boost::noncopyable {
public:
/**
* the glib main loop passed to create() or connect() in one of
* the derived classes
*/
GMainLoopPtr getLoop() const;
/**
* Called when the D-Bus connection is up and running. It is ready
* to register objects that the peer might need. It is
* guaranteed that any objects registered now will be ready before
* the helper gets a chance to make D-Bus calls.
*/
typedef boost::signals2::signal<void (const GDBusCXX::DBusConnectionPtr &)> OnConnect;
OnConnect m_onConnect;
protected:
ForkExec(const GMainLoopPtr &loop);
};
/**
* The parent side of a fork/exec.
*/
class ForkExecParent : public ForkExec
{
public:
/**
* A ForkExecParent instance must be created via this factory
* method and then be tracked in a shared pointer. This method
* will not start the helper yet: first connect your slots, then
* call start().
*/
static boost::shared_ptr<ForkExecParent> create(const GMainLoopPtr &loop,
const std::string &helper);
/**
* the helper string passed to create()
*/
std::string getHelper() const;
/**
* run the helper executable in the parent process
*/
void start();
/**
* request that the child process terminates by sending it a
* SIGINT
*/
void stop();
/**
* kill the child process without giving it a chance to shut down
* by sending it a SIGKILL
*/
void kill();
/**
* Called when the helper has quit. The parameter of the signal is
* the return status of the helper (see waitpid()).
*/
typedef boost::signals2::signal<void (int)> OnQuit;
OnQuit m_onQuit;
private:
ForkExecParent(const GMainLoopPtr &loop,
const std::string &helper);
};
/**
* The child side of a fork/exec.
*
* At the moment, the child cannot monitor the parent or kill it.
* Might be added (if needed), in which case the corresponding
* ForkExecParent members should be moved to the common ForkExec.
*/
class ForkExecChild : public ForkExec
{
public:
/**
* A ForkExecChild instance must be created via this factory
* method and then be tracked in a shared pointer. The process
* must have been started by ForkExecParent (directly or indirectly)
* and any environment variables set by ForkExecParent must still
* be set.
*/
static boost::shared_ptr<ForkExecChild> create(const GMainLoopPtr &loop);
/**
* true if the current process was created by ForkExecParent
*/
static bool wasForked();
private:
ForkExecChild(const GMainLoopPtr &loop);
};
SE_END_CXX
#endif // HAVE_GLIB
#endif // INCL_FORK_EXEC
#include "gdbus-cxx-bridge.h"
#include <syncevo/GLibSupport.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/ForkExec.h>
#include <iostream>
#include <signal.h>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
SE_GOBJECT_TYPE(GMainLoop);
SyncEvo::GMainLoopPtr loop;
class Test
{
GDBusCXX::DBusObjectHelper m_server;
// GDBusCXX::DBusRemoteObject m_dbusAPI;
// GDBusCXX::SignalWatch0 m_disconnected;
public:
Test(const GDBusCXX::DBusConnectionPtr &conn) :
// will close connection
m_server(conn, "/test", "org.example.Test", GDBusCXX::DBusObjectHelper::Callback_t(), true)
// m_dbusAPI(conn, DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "" /* sender? */),
// m_disconnected(m_dbusAPI, "Disconnected")
{
m_server.add(this, &Test::hello, "Hello");
}
void activate()
{
m_server.activate();
// fails with an unspecific error inside libdbus, don't rely on "Disconnected"
// m_disconnected.activate(boost::bind(&Test::disconnected, this));
// not implemented either
// b_dbus_set_disconnect_function(m_server.getConnection(),
// staticDisconnected,
// NULL,
// NULL);
}
std::string hello(const std::string &in)
{
std::cout << "hello() called with " << in << std::endl;
return "world";
}
void disconnected()
{
std::cout << "connection disconnected";
}
// static void staticDisconnected(DBusConnection*conn, void *data)
// {
// std::cout << "connection disconnected";
// }
};
static void newClientConnection(GDBusCXX::DBusServerCXX &server, GDBusCXX::DBusConnectionPtr &conn,
boost::scoped_ptr<Test> &testptr)
{
std::cout << "new connection, " <<
(dbus_connection_get_is_authenticated(conn.get()) ? "authenticated" : "not authenticated") <<
std::endl;
testptr.reset(new Test(conn.get()));
testptr->activate();
}
static void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn,
boost::scoped_ptr<Test> &testptr)
{
std::cout << "child is ready, " <<
(dbus_connection_get_is_authenticated(conn.get()) ? "authenticated" : "not authenticated") <<
std::endl;
testptr.reset(new Test(conn.get()));
testptr->activate();
}
static void onQuit(int status)
{
std::cout << "child has quit, status " << status << std::endl;
g_main_loop_quit(loop.get());
}
class TestProxy : public GDBusCXX::DBusRemoteObject
{
public:
TestProxy(const GDBusCXX::DBusConnectionPtr &conn) :
GDBusCXX::DBusRemoteObject(conn.get(), "/test", "org.example.Test", "direct.peer"),
m_hello(*this, "Hello") {
}
GDBusCXX::DBusClientCall1<std::string> m_hello;
};
static void helloCB(GMainLoop *loop, const std::string &res, const std::string &error)
{
if (!error.empty()) {
std::cout << "call failed: " << error << std::endl;
} else {
std::cout << "hello('hello') = " << res << std::endl;
}
g_main_loop_quit(loop);
}
static void callServer(const GDBusCXX::DBusConnectionPtr &conn)
{
TestProxy proxy(conn);
proxy.m_hello(std::string("world"), boost::bind(helloCB, loop.get(), _1, _2));
}
void signalHandler (int sig)
{
if (loop) {
g_main_loop_quit(loop.get());
}
}
int main(int argc, char **argv)
{
int ret = 0;
signal (SIGABRT, &signalHandler);
signal (SIGTERM, &signalHandler);
signal (SIGINT, &signalHandler);
try {
gboolean opt_server;
gboolean opt_fork_exec;
gchar *opt_address;
GOptionContext *opt_context;
// gboolean opt_allow_anonymous;
SyncEvo::GErrorCXX gerror;
GDBusCXX::DBusErrorCXX dbusError;
GOptionEntry opt_entries[] = {
{ "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL },
{ "forkexec", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Use fork+exec to start the client (implies --server)", NULL },
{ "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL },
// { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL },
{ NULL}
};
g_type_init();
opt_address = NULL;
opt_server = FALSE;
opt_fork_exec = FALSE;
// opt_allow_anonymous = FALSE;
opt_context = g_option_context_new("peer-to-peer example");
g_option_context_add_main_entries(opt_context, opt_entries, NULL);
if (!g_option_context_parse(opt_context, &argc, &argv, gerror)) {
gerror.throwError("parsing command line options");
}
// if (!opt_server && opt_allow_anonymous) {
// throw stdruntime_error("The --allow-anonymous option only makes sense when used with --server.");
// }
loop.set(g_main_loop_new (NULL, FALSE), "main loop");
if (opt_fork_exec) {
boost::scoped_ptr<Test> testptr;
boost::shared_ptr<SyncEvo::ForkExecParent> forkexec =
SyncEvo::ForkExecParent::create(loop,
argv[0]);
forkexec->m_onConnect.connect(boost::bind(onChildConnect, _1, boost::ref(testptr)));
forkexec->m_onQuit.connect(onQuit);
g_main_loop_run(loop.get());
} else if (opt_server) {
boost::shared_ptr<GDBusCXX::DBusServerCXX> server =
GDBusCXX::DBusServerCXX::listen(opt_address ?
opt_address : "",
&dbusError);
if (!server) {
dbusError.throwFailure("starting server");
}
std::cout << "Server is listening at: " << server->getAddress() << std::endl;
boost::scoped_ptr<Test> testptr;
server->setNewConnectionCallback(boost::bind(newClientConnection, _1, _2, boost::ref(testptr)));
g_main_loop_run(loop.get());
} else if (SyncEvo::ForkExecChild::wasForked()) {
boost::shared_ptr<SyncEvo::ForkExecChild> forkexec =
SyncEvo::ForkExecChild::create(loop);
forkexec->m_onConnect.connect(callServer);
g_main_loop_run(loop.get());
} else {
if (!opt_address) {
throw std::runtime_error("need server address");
}
GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection(opt_address,
&dbusError);
if (!conn) {
dbusError.throwFailure("connecting to server");
}
// closes connection
GDBusCXX::DBusObject guard(conn, "foo", "bar", true);
callServer(conn);
g_main_loop_run(loop.get());
}
loop.set(NULL);
} catch (const std::exception &ex) {
std::cout << ex.what() << std::endl;
ret = 1;
loop.set(NULL);
}
std::cout << "server done" << std::endl;
return ret;
}
_______________________________________________
SyncEvolution mailing list
[email protected]
http://lists.syncevolution.org/listinfo/syncevolution