UI-Dispatch: document some implementation intricacies

This commit is contained in:
Fischlurch 2017-08-10 19:26:47 +02:00
parent d8c19c7a32
commit 805e6047ba
3 changed files with 65 additions and 14 deletions

View file

@ -36,6 +36,37 @@
** subsystems is not deterministic, this rules out passing references to anything tied to some
** subsystem lifecycle. Referring to a static singleton is acceptable though.
**
** # implementation considerations
**
** Basically the implementation relies on the [standard mechanism][Gtkmm-tutorial] for multithreaded
** UI applications. But on top we use our own [dispatcher queue](\ref lib::CallQueue) to allow passing
** arbitrary argument data, based on the argument storage of `std::function`. Which in the end effectively
** involves two disjoint thread collaboration mechanisms:
** - the caller creates a closure of the operation to be invoked, binding all arguments by value
** - this closure is wrapped into a `std::function` instance
** - which in turn is added into the dispatcher queue. Depending on the implementation,
** this might incur explicit locking
** - after successfully enqueuing the closure, the GTK event thread is signalled through
** the [Glib-Dispatcher], which actually messages through an OS-pipe (kernel based IO)
** - the Dispatcher need to be created within the UI event thread (which is the case, since
** all of Lumiera's UI top-level context is created in the thread dedicated to run GTK)
** - relying on internal GLib / GIO ``magic'', the dispatcher hooks into the respective GLib
** ``main context'' to ensure this signalling is picked up from the event thread, which...
** - ...finally leads to invocation of the Dispatcher's signal from within the event loop
**
** This hybrid approach is rather simple to establish, but creates additional complexities at runtime.
** More specifically, we have to pay the penalty of chaining the overhead and the inherent limitations
** of two thread signalling mechanisms (in our dispatcher queue and within kernel based IO).
** It would be conceivable to implement all of the hand-over mechanism solely within our framework,
** yet unfortunately there seems to be no easily accessible and thus ``official'' way to hook into
** the event processing, at least without digging deep into GLib internals.
**
** @note as detailed in the documentation for [Glib-Dispatcher], all instances built within a
** given receiver thread (here the UI event loop thread) will _share a single pipe for signalling._
** Under heavy load this queue might fill up and block the sender on dispatch.
**
** [Glib-Dispatcher]: https://developer.gnome.org/glibmm/2.42/classGlib_1_1Dispatcher.html
** [GTKmm-tutorial]: https://developer.gnome.org/gtkmm-tutorial/3.12/sec-using-glib-dispatcher.html.en
** @see NotificationService
** @see CallQueue_test
*/
@ -48,22 +79,21 @@
#include "lib/call-queue.hpp"
#include <boost/noncopyable.hpp>
//#include <string>
#include <utility>
namespace gui {
namespace ctrl {
// using std::string;
using std::move;
// class GlobalCtx;
/**
* Helper to dispatch code blocks into the UI event thread for execution.
* Implementation of the actual dispatch is based on `Glib::Dispatcher`
* @warning any UiDispatcher instance must be created such as to ensure
* it outlives the GTK event loop
*/
class UiDispatcher
: boost::noncopyable

View file

@ -24,9 +24,25 @@
** Implementation of notifications and updates within the GUI.
** This is the actual service implementation and runs within the GUI plugin.
**
** @todo 1/2017 actually forward invocations on the UI-Bus
** Since GTK is _not threadsafe by design,_ any external invocation passed through
** this facade service will be dispatched explicitly into the GTK event loop thread.
** The implementation of this dispatch is based upon `Glib::Dispatcher` and thus
** requires this service instance to be created from within the thread performing
** the GTK event loop. Moreover, to avoid segmentation faults on shutdown, the
** lifespan of this service instance must exceed the running of the event loop,
** since otherwise the event loop might invoke a lambda bound to the `this`
** pointer of a NotificationService already decommissioned. The setup of the
** standard Lumiera UI top-level context ensures this is the case, since the
** UiManager::performMainLoop() maintains the NotificationService instance
** and also performs the blocking `gtk_main()` call. Consequently, any
** invocation added from other threads after leaving the GTK main loop
** but before closing the GuiNotification facade will just be enqueued,
** but then dropped on destruction of the UiDispatcher PImpl.
**
** @todo 1/2017 find a solution for passing diff messages /////////////////////////////////////////////////TICKET #1066
**
** @see ui-dispatcher.hpp
**
*/
@ -73,7 +89,7 @@ namespace gui {
void
NotificationService::displayInfo (string const& text)
{
INFO (gui, "@GUI: display '%s' as notification message.", cStr(text));
INFO (gui, "@GUI: display '%s' as notification message.", cStr(text)); ///////////////////////////////////TICKET #1102 : build a message display box in the UI
////////////////////////TODO actually push the information to the GUI ///////////////////////////////////TICKET #1098 : use a suitable Dispatcher
}

View file

@ -1034,11 +1034,17 @@
<node CREATED="1501938690626" ID="ID_353231581" MODIFIED="1501938729247" TEXT="thread safe queue"/>
<node CREATED="1501938705321" ID="ID_1924064403" MODIFIED="1501938711076" TEXT="lambdas f&#xfc;r Argument-binding"/>
</node>
<node CREATED="1501938776839" ID="ID_1031878628" MODIFIED="1502371627144" TEXT="Anwendung explizit">
<icon BUILTIN="pencil"/>
<node CREATED="1501939110138" ID="ID_203135379" MODIFIED="1501939129491" TEXT="Lambda konstruieren"/>
<node CREATED="1501939132431" ID="ID_1400757157" MODIFIED="1501939149321" TEXT="Funktion zum Triggern"/>
<node CREATED="1501939149837" ID="ID_1733976265" MODIFIED="1501939157472" TEXT="diese an Glib::Dispatcher binden"/>
<node COLOR="#338800" CREATED="1501938776839" ID="ID_1031878628" MODIFIED="1502385810866" TEXT="Convenience-Wrapper">
<icon BUILTIN="button_ok"/>
<node CREATED="1501939110138" ID="ID_203135379" MODIFIED="1502385815083" TEXT="Lambda konstruieren">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1501939132431" ID="ID_1400757157" MODIFIED="1502385817834" TEXT="Funktion zum Triggern">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1501939149837" ID="ID_1733976265" MODIFIED="1502385820938" TEXT="diese an Glib::Dispatcher binden">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1501939174506" ID="ID_504999068" MODIFIED="1501939191040" TEXT="in NotificationService integrieren">
<icon BUILTIN="flag-yellow"/>
@ -1048,7 +1054,9 @@
<node CREATED="1502375146395" ID="ID_144811928" MODIFIED="1502378036357" TEXT="CallQueue einrichten">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1502375120551" ID="ID_1281399419" MODIFIED="1502375131121" TEXT="Glib::Dispatcher verwenden"/>
<node CREATED="1502375120551" ID="ID_1281399419" MODIFIED="1502385766022" TEXT="Glib::Dispatcher verwenden">
<icon BUILTIN="button_ok"/>
</node>
<node CREATED="1502375134501" ID="ID_1011463592" MODIFIED="1502375142424" TEXT="Aufrufe in Lamdas verpacken"/>
</node>
<node CREATED="1502375294344" ID="ID_640199096" MODIFIED="1502375310242" TEXT="Spezialbehandlung f&#xfc;r Shutdown">
@ -1073,9 +1081,6 @@
</node>
<node CREATED="1502375371230" ID="ID_259224346" MODIFIED="1502375379121" TEXT="eigene Dispatcher-Instanz"/>
</node>
<node CREATED="1501939160907" ID="ID_188350210" MODIFIED="1502371616753" TEXT="Convenience-Wrapper">
<icon BUILTIN="help"/>
</node>
<node CREATED="1501939193031" HGAP="3" ID="ID_1045913810" MODIFIED="1501939200829" TEXT="Test" VSHIFT="20">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1501939204166" ID="ID_383444966" MODIFIED="1502075950777" TEXT="CallQueue_test">