notifier.h
Go to the documentation of this file.
/* Copyright (C) 2005 to 2013 Chris Vine
The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:
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) any later version.
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, version 2.1, for more details.
You should have received a copy of the GNU Lesser General Public
License, version 2.1, along with this library (see the file LGPL.TXT
which came with this source code package in the c++-gtk-utils
sub-directory); if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef CGU_NOTIFIER_H
#define CGU_NOTIFIER_H
/**
* @file notifier.h
* @brief This file provides a Notifier class to provide thread-safe
* signalling between a worker thread and the main program thread.
*
* For further details read this: Notifier.
*/
#include <unordered_set>
#include <type_traits> // for std::remove_reference, std::enable_if and std::is_convertible
#include <pthread.h>
#include <c++-gtk-utils/pipes.h>
#include <c++-gtk-utils/io_watch.h>
#include <c++-gtk-utils/emitter.h>
#include <c++-gtk-utils/cgu_config.h>
namespace Cgu {
/**
* @class Notifier notifier.h c++-gtk-utils/notifier.h
* @brief Provides thread-safe signalling between a worker thread and
* the main program thread.
* @sa Callback namespace Callback::post()
* @sa EmitterArg SafeEmitterArg Releaser
*
* The Notifier class provides thread-safe signalling between two
* threads. It does this through a pipe, to which an GSource (iowatch)
* object is attached to connect it to the glib program event loop.
* Callable objects (such as lambda expressions or the return value of
* std::bind) or Callback::SafeFunctor objects may be connected to the
* Notifier (referred to be below as "connected callables"), which are
* called in the receiving thread via the program event loop when
* operator()() (or emit()) is called on the Notifier object by the
* signalling thread. It therefore behaves like a SafeEmitter object,
* except that connected callables execute in the glib program event
* loop thread rather than in the thread which calls
* operator()()/emit().
*
* It is an alternative to the Callback::post() function in
* callback.h, and the documentation on callback.h contains a
* description of relevant trade-offs between the two.
*
* If the signalling thread is the same thread as that in which the
* connected callables will execute (which is the thread in which the
* default glib program event loop executes), executing them via the
* pipe would risk a deadlock - if the pipe fills up, the thread would
* block on write and never be able to read from the pipe to empty it.
* Accordingly, if the Notifier object is invoked by the same thread
* as that in which the connected callables will execute, this is
* detected and they will be invoked directly, rather than via the
* pipe. Therefore, actions so invoked may be out of order with those
* invoked by the other threads.
*
* If a Releaser object is passed as the second argument of
* Notifier::connect(), then the connected callable concerned will
* automatically be disconnected if the object which has the Releaser
* object as a member is destroyed.
*
* The main use of Notifier objects is for a worker thread to signal
* an event to the main thread in which GTK+ is executing, which
* implies that GTK+ should also be executing in the default glib
* program event loop (GMainContext) (as will almost always be the
* case), which is the one with which the program first starts.
* Before a Notifier object is first used, it is a requirement that
* Notifier::init() (a static member function) be called in the thread
* in which the default glib event loop executes, and any connected
* callables will execute in that thread. Notifier::init() only needs
* to be called once at program start-up - it doesn't need to be
* called separately for each Notifier object, and can be called
* before any Notifier objects have been constructed. If it has not
* been called before the construction of the first Notifier object
* has taken place, it will occur automatically on that first
* construction. That means that if the first Notifier object is not
* constructed in the main (event loop) thread of the program, then
* Notifier::init() must be called explicitly before that first object
* is constructed. In addition, if glib < 2.32 is installed, before
* Notifier::init() is called (or the first Notifier object created)
* g_thread_init(0) should have been called: as a result a Notifier
* object cannot be a global (non-local) static object with glib <
* 2.32 (glib >= 2.32 does not require g_thread_init() to be called to
* be thread safe). Note also that global static Notifier objects are
* not safe prior to version 2.0.15, even with glib >= 2.32.
*
* It is a good idea that Notifier::init() should have been called (or
* the first Notifier object constructed) before the main program
* thread creates any new threads. Then the state of initialisation
* effected by Notifier::init() will automatically be visible between
* threads.
*
* When executing connected callables, a check is made for a case
* where between the signalling thread invoking a Notifier object and
* the main program event loop executing the callables, the Notifier
* object ceases to exist. However there can still be a race
* condition if the lifetime of the Notifier object is determined
* outside the thread of execution of the main program event loop and
* a Notifier object is destroyed by that other thread between the
* time the check is made and the callables executed. Normally
* Notifier objects are constructed and destroyed in the main program
* thread, but where that is not the case the user will need to take
* this into account and if need be provide appropriate
* synchronisation to secure the lifetime of the Notifier object until
* after the callables have been executed. Likewise, a Releaser
* object cannot offer protection if the remote object whose
* non-static method is represented or called into by a connected
* callable is destroyed by another thread while the main program loop
* is in the middle of executing the callable. When the main loop
* begins executing a callable, the remote object must either wholly
* exist (in which case the callable will be invoked) or have been
* destroyed (in which case the callable will be ignored), and not be
* in some transient half-state governed by another thread.
*
* Apart from that, the Notifier object is thread-safe and any of its
* methods may be invoked in any thread. (It is as thread-safe as a
* SafeEmitter object, as described in emitter.h, which contains
* further details on thread safety.)
*
* To pass variable data to a connected callable, the AsyncQueue class
* can be employed.
*
* @b Usage
*
* This is an example:
*
* @code
* using namespace Cgu;
*
* // assume a Notifier object 'notifier' has been constructed at some other point in the program
* notifier.connect([] () {std::cout << "Hello world\n";});
* notifier(); // callable executes in glib main loop
* @endcode
*
* Callback::SafeFunctor objects may be connected to a notifier, and
* the connect() method may be directly initialized with the result of
* Callback::make(), Callback::make_ref() or Callback::lambda() and
* implicit conversion will take place. Here is an example using
* Callback::make_ref(), with a class object my_obj of type MyClass,
* with a method void MyClass::my_method(const Something&, int):
*
* @code
* using namespace Cgu;
*
* int arg1 = 1;
* Something arg2;
* notifier.connect(Callback::make_ref(my_obj, &MyClass::my_method, arg1, arg2));
* notifier(); // callable executes in glib main loop
* @endcode
*
*/
/*
For a program with two GMainContext program event loops (not a usual
case), it would be possible for a Notifier-like object to be
initialised in the non-default GMainContext thread, and execute in
that thread, by passing that other GMainContext object as the last
argument when calling start_iowatch() in Notifier::Notifier().
However, to conserve file descriptors all Notifier objects share a
common pipe and iowatch event watch, which implies that all Notifier
objects would also need to execute in that other thread. To get
around this it would be possible either to templatize Notifier with
tag types for different GMainContexts (so that there would be a
different static pipe/iowatch object for each GMainContext), or to
have thread-local storage for each of the static objects in the
Notifier class, but an easier solution for one-off cases would be to
have a version of Notifier which does not use static (shared)
PipeFifo and iowatch objects, at the expense of greater use of file
descriptor resources.
Such a special Notifier object could also be used to signal from a
Unix (asynchronous) signal/interrupt handler, but in that case the
write file descriptor of the pipe should be set non-blocking to
prevent the very unlikely but theoretically possible case (in a
program executing in a system under extreme load) of the pipe
filling up before being emptied by the Notifier::read_pipe_cb()
callback function executing in the main program and so blocking in
the handler, thus deadlocking the program.
*/
namespace Thread {
class Mutex;
}
class Notifier;
class Notifier {
static bool initialised;
static pthread_t thread_id;
// pointers can be keys of associative containers: "For templates
// greater, less, greater_equal, and less_equal, the specializations
// for any pointer type yield a total order, even if the built-in
// operators <, >, <=, >= do not." (para 20.3.3/8).
static std::unordered_set<Notifier*>* object_set_p;
static PipeFifo* pipe_p;
static Thread::Mutex* set_mutex_p;
static Thread::Mutex* write_mutex_p;
static void read_pipe_cb(bool&);
SafeEmitter emitter;
public:
/**
* This class cannot be copied. The copy constructor is deleted.
*/
Notifier(const Notifier&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*/
Notifier& operator=(const Notifier&) = delete;
/**
* A utility which tells the caller whether it is in the thread in
* which the callback will execute (the main program thread). It will
* not throw. It is thread safe.
* @return true if the caller is in the thread in which the callback
* will execute, otherwise false.
*/
// don't make this a static member function - it can then only be called
// by object notation after a Notifier object has first been constructed,
// which means Notifier::init() must have been called
bool in_main_thread() {return pthread_equal(thread_id, pthread_self());}
/**
* This will cause the connected functors to be executed in the main
* program thread. It is thread safe (but see the comments in the
* introductory remarks above about race conditions where the lifetime
* of a Notifier object is determined by a thread other than the main
* program thread, and about protection by a Releaser object where a
* connected remote object is destroyed in mid-emission by another
* thread).
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case, and this
* method is called in the thread in which the functors will execute
* (the main program thread). In addition, it will throw if the
* function or class methods represented by the functors throw (or if
* the assignment operator of a bound argument throws) and the call is
* made in that thread. If called in a different thread it will not
* throw (an exception thrown by a connected functor will be caught
* and reported in the iowatch dispatcher).
*/
void emit();
/**
* This will cause the connected functors to be executed in the main
* program thread. It is thread safe (but see the comments in the
* introductory remarks above about race conditions where the lifetime
* of a Notifier object is determined by a thread other than the main
* program thread, and about protection by a Releaser object where a
* connected remote object is destroyed in mid-emission by another
* thread).
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case, and this
* method is called in the thread in which the functors will execute
* (the main program thread). In addition, it will throw if the
* function or class methods represented by the functors throw (or if
* the assignment operator of a bound argument throws) and the call is
* made in that thread. If called in a different thread it will not
* throw (an exception thrown by a connected functor will be caught
* and reported in the iowatch dispatcher).
*/
void operator()() {emit();}
/**
* Connects a functor. It is thread safe.
* @param f The functor to connect.
* @return The functor connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::SafeFunctor connect(const Callback::SafeFunctor& f);
/**
* Connects a functor. It is thread safe.
* @param f The functor to connect.
* @param r A Releaser object for automatic disconnection of the
* functor if the object whose method it represents is destroyed.
* @return The functor connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::SafeFunctor connect(const Callback::SafeFunctor& f, Releaser& r);
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. It must be fully bound
* (that is, take no arguments when called).
* @return A Callback::SafeFunctor object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctor object or a pointer to a Callback::Callback
// object or some other convertible object, this templated overload is
// dropped from the overload set, in order to support the
// Callback::SafeFunctor overloads of this function. This overload
// calls into the version of this function taking a
// Callback::SafeFunctor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::SafeFunctor>::value>::type>
Callback::SafeFunctor connect(F&& f) {
return connect(Callback::lambda<>(std::forward<F>(f)));
}
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. It must be fully bound
* (that is, take no arguments when called).
* @param r A Releaser object for automatic disconnection of the
* callable object if an object whose method it represents or calls
* into is destroyed.
* @return A Callback::SafeFunctor object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctor object or a pointer to a Callback::Callback
// object or some other convertible object, this templated overload is
// dropped from the overload set, in order to support the
// Callback::SafeFunctor overloads of this function. This overload
// calls into the version of this function taking a
// Callback::SafeFunctor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::SafeFunctor>::value>::type>
Callback::SafeFunctor connect(F&& f, Releaser& r) {
return connect(Callback::lambda<>(std::forward<F>(f)), r);
}
/**
* Disconnects a functor previously connected. This does not throw
* provided that the destructors of any bound arguments do not throw.
* It is thread safe.
* @param f The functor to disconnect.
*/
void disconnect(const Callback::SafeFunctor& f);
/**
* Blocks a connected functor from executing in the main program
* thread when emit() or operator()() is called until unblock() is
* called. This method does not throw. It is thread safe.
* @param f The functor to block.
* @note This has effect immediately: it will block a pending emission
* for which emit() or operator()() has previously been called but
* which has not yet been tested for execution in the main loop.
*/
void block(const Callback::SafeFunctor& f);
/**
* Unblocks a previously blocked functor. This method does not throw.
* It is thread safe.
* @param f The functor to unblock.
* @note This has effect immediately: it will unblock a pending
* emission for which emit() or operator()() has previously been
* called but which has not yet been tested for execution in the main
* loop.
*/
void unblock(const Callback::SafeFunctor& f);
/**
* Initialises the program for the use of Notifier objects. It only
* needs to be called once at program start-up (it doesn't need to be
* called separately for each Notifier object), and can be called
* before any Notifier objects have been constructed. It should be
* called in the thread in which the default main glib event loop
* executes (the main program thread) before that thread creates any
* new threads.
* @exception std::bad_alloc This method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
* @exception PipeError PipeError will be thrown if the static pipe
* used by Notifier objects cannot be initialised.
*/
static void init();
/**
* The constructor is thread safe provided init() has previously been
* called before the main program thread creates any new threads.
* @exception std::bad_alloc The constructor might throw
* std::bad_alloc if memory is exhausted and the system throws in that
* case.
* @exception PipeError PipeError can be thrown if this is the first
* Notifier object to be constructed and Notifier::init() has not
* previously been called.
*/
Notifier();
/**
* The destructor does not throw provided the destructors of any bound
* arguments do not throw and std::hash<T*>::operator()() does not
* throw (as it would not on any sane implementation). It is thread
* safe (but see the comments in the introductory remarks above about
* race conditions where the lifetime of a Notifier object is
* determined by a thread other than the main program thread).
*/
~Notifier();
/* Only has effect if --with-glib-memory-slices-compat or
* --with-glib-memory-slices-no-compat option picked */
CGU_GLIB_MEMORY_SLICES_FUNCS
};
} // namespace Cgu
#endif