Project

General

Profile

Advice needed: Wt + Qt (with DBus) + boost

Added by Russ Freeman about 10 years ago

Hi everyone,

I have a Wt server up and working just great. My startup looks something like this with the Qt event loop as the primary thread:

  QCoreApplication app( argc, argv );

  WServer server(argv[0], "");

  server.setServerConfiguration(argc, argv, WTHTTP_CONFIGURATION);
  server.addEntryPoint(Application, createApplication);
  server.start();

  int nret = app.exec();

  ...

  return nret;

I have a global singleton connecting to a DBus server which is periodically sending it signals to update the Wt server.

I'm then using boost to connect Wt widgets to my global singleton so that they take their own updates.

However, I'm struggling with how to update the running apps. Wt::WApplication::instance() returns NULL because it's, I guess, being called in the Qt/DBus thread so I can't lock/trigger update.

I'm expecting to have a few of these updates from DBus so I'm trying to make something that is simple, elegant and extensible but wondered what others thoughts may be.

Any suggestions welcomed :-)


Replies (3)

RE: Advice needed: Wt + Qt (with DBus) + boost - Added by Wim Dumon about 10 years ago

Hello Russ,

Your analysis is correct - the thread is not bound to a Wt session.

The most elegant solution is to use WServer::post() to send events to your Wt sessions. See also WApplication::bind.

Alternatively, grab the application's UpdateLock, which will as a side effect also tie your Qt thread to the WApplication who's lock is taken. But ensuring proper locking (including avoiding deadlocks) is entirely up to you if you go this route.

Best regards,

Wim.

RE: Advice needed: Wt + Qt (with DBus) + boost - Added by Russ Freeman about 10 years ago

Thanks Wim. I assumed as much but it's great to have confirmation.

RE: Advice needed: Wt + Qt (with DBus) + boost - Added by Russ Freeman about 10 years ago

In order to save others from having the same problems - and also so my code can be reviewed :) below is my solution.

It functions in a similar way to the usual signals, connect/disconnect, but the signal is sent via WServer::post().

#ifndef POSTEDSIGNAL_H
#define POSTEDSIGNAL_H

#include <boost/thread/mutex.hpp>
#include <wt/WApplication>
#include <wt/WServer>

/*
#ifndef POSTEDSIGNAL_H
#include "PostedSignal.h"
#endif
*/

/**
    \brief A utility class for use within Wt applications that behaves a little like boost signals.

    Because it uses the Wt::WServer::post function it is quite safe to use from non-Wt threads to
    post events to wt objects.

    You must call disconnect!

    */
class PostedSignal {
public:
#ifdef _DEBUG
    /**
        \brief Debug only destructor

        All this does is assert that the connection list must be empty when it's destroyed.

        If not then you have not disconnected enough times :-)
     */
    ~PostedSignal()
    {
        //  Did you forget to call disconnect on something?
        assert( m_connections.size() == 0 );
    }
#endif  //  _DEBUG

    /**
        \brief Connect a function to this signal

        It assumes the application instance.
     */
    void connect(const boost::function<void()>& function)
    {
        boost::mutex::scoped_lock lock( m_connectionsMutex );
        assert( Wt::WApplication::instance() );
        m_connections.push_back( Connection(Wt::WApplication::instance()->sessionId(), function) );
    }

    /**
        \brief Disconnect. It needs to be called!

        It assumes the application instance.
     */
    void disconnect()
    {
        boost::mutex::scoped_lock lock(m_connectionsMutex);
        assert( Wt::WApplication::instance() );
#ifdef _DEBUG
        bool bFound = false;
#endif  //   _DEBUG

        const std::string currentSession = Wt::WApplication::instance()->sessionId();

        //
        //  Loop like this because we could, in theory, connect twice for different functions
        //  for the same sessionId.
        std::vector<Connection>::iterator it( m_connections.begin() );
        while(it != m_connections.end())
        {
            if(it->m_sessionId == currentSession)
            {
                it = m_connections.erase(it);
#ifdef _DEBUG
                bFound = true;
#endif  //  _DEBUG
            }
            else
            {
                ++it;
            }
        }

        //  Couldn't find the session. Did you connect?
        assert(bFound);
    }


    /**
        \brief Signal this event.

        Notifies all registered functions of the event by posting to the server instance.
     */
    void emit() const
    {
        assert( Wt::WServer::instance() );

        boost::mutex::scoped_lock lock(m_connectionsMutex);
        std::vector<Connection>::const_iterator it;
        for ( it=m_connections.begin() ; it < m_connections.end(); it++ )
        {
            Wt::WServer::instance()->post(it->m_sessionId, it->m_function);
        }
    }

private:
  struct Connection {
    Connection(const std::string& id, const boost::function<void()>& f)
        : m_sessionId(id)
        , m_function(f)
        { }

    std::string m_sessionId;
    boost::function<void()> m_function;
  };
    std::vector<Connection> m_connections;
  mutable boost::mutex m_connectionsMutex;
};

#endif //POSTEDSIGNAL_H

Usage

PostedSignal sig;

...in a widget constructor...

sig.connect( boost::bind( &MyWidget::update, this ) );

...in a widget destructor...

sig.disconnect();

...and to send the signal from different threads...

sig.emit();

If anyone spots an error in the code or can suggest a better way that would be great :-)

    (1-3/3)