Documentation/Maemo 5 Developer Guide/DBus/Asynchronous Glib/D-Bus

(categorise)
(wikify slightly, use <source>)
Line 1: Line 1:
-
=Asynchronous GLib/D-Bus =
 
-
 
The example code used below can be found in the [https://garage.maemo.org/svn/maemoexamples/trunk/glib-dbus-signals/ garage svn].
The example code used below can be found in the [https://garage.maemo.org/svn/maemoexamples/trunk/glib-dbus-signals/ garage svn].
-
 
-
 
==  Asynchronicity in D-Bus clients ==
==  Asynchronicity in D-Bus clients ==
Line 17: Line 13:
== Slow Test Server ==
== Slow Test Server ==
-
The only change on the server side is the addition of delays into each of the RPC methods (setvalue1, setvalue2, getvalue1 and getvalue2). This delay is added to the start of each function as follows: glib-dbus-async/server.c
+
The only change on the server side is the addition of delays into each of the RPC methods (<code>setvalue1</code>, <code>setvalue2</code>, <code>getvalue1</code> and <code>getvalue2</code>). This delay is added to the start of each function as follows: glib-dbus-async/server.c
-
<tt><span>''<span><font color="#9A1900">/* How many microseconds to delay between each client operation. */</font></span>''</span>
+
<source lang="c">
-
<span>'''<span><font color="#000080"><nowiki>#define</nowiki></font></span>'''</span> <span>'''<span><font color="#000000">SERVER_DELAY_USEC</font></span>'''</span> <span><font color="#990000">(</font></span><span><font color="#993399">5</font></span><span><font color="#990000"><nowiki>*</nowiki></font></span>1000000UL<span><font color="#990000">)</font></span>
+
/* How many microseconds to delay between each client operation. */
-
  <span>''<span><font color="#9A1900">/*... Listing cut for brevity ...*/</font></span>''</span>
+
#define SERVER_DELAY_USEC (5*1000000UL)
-
gboolean <span>'''<span><font color="#000000">value_object_setvalue1</font></span>'''</span><span><font color="#990000">(</font></span>ValueObject<span><font color="#990000"><nowiki>*</nowiki></font></span> obj<span><font color="#990000">,</font></span> gint valueIn<span><font color="#990000">,</font></span>
+
  /*... Listing cut for brevity ...*/
-
                                                  GError<span><font color="#990000"><nowiki>**</nowiki></font></span> error<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
gboolean value_object_setvalue1(ValueObject* obj, gint valueIn,
-
  <span>'''<span><font color="#000000">dbg</font></span>'''</span><span><font color="#990000">(</font></span><span><font color="#FF0000">"Called (valueIn=%d)"</font></span><span><font color="#990000">,</font></span> valueIn<span><font color="#990000">);</font></span>
+
                                                  GError** error) {
-
  <span>'''<span><font color="#000000">g_assert</font></span>'''</span><span><font color="#990000">(</font></span>obj <span><font color="#990000"><nowiki>!=</nowiki></font></span> NULL<span><font color="#990000">);</font></span>
+
  dbg("Called (valueIn=%d)", valueIn);
-
  <span>'''<span><font color="#000000">dbg</font></span>'''</span><span><font color="#990000">(</font></span><span><font color="#FF0000">"Delaying operation"</font></span><span><font color="#990000">);</font></span>
+
  g_assert(obj != NULL);
-
  <span>'''<span><font color="#000000">g_usleep</font></span>'''</span><span><font color="#990000">(</font></span>SERVER_DELAY_USEC<span><font color="#990000">);</font></span>
+
  dbg("Delaying operation");
-
  <span>''<span><font color="#9A1900">/* Compare the current value against old one. If they are the same,</font></span>''</span>
+
  g_usleep(SERVER_DELAY_USEC);
-
<span>''<span><font color="#9A1900">    we do not need to do anything (except return success). */</font></span>''</span>
+
  /* Compare the current value against old one. If they are the same,
-
  <span>'''<span><font color="#0000FF">if</font></span>'''</span> <span><font color="#990000">(</font></span>obj<span><font color="#990000">-&gt;</font></span>value1 <span><font color="#990000"><nowiki>!=</nowiki></font></span> valueIn<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span></tt>
+
    we do not need to do anything (except return success). */
 +
  if (obj->value1 != valueIn) {
 +
</source>
Building the server is done as before, but we notice the delay when we call an RPC method:
Building the server is done as before, but we notice the delay when we call an RPC method:
-
<div class="graybox">
+
<pre>
-
[sbox-FREMANTLE_X86: ~/glib-dbus-async] &gt; run-standalone.sh ./server &amp;
+
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > run-standalone.sh ./server &
-
server:main Connecting to the Session D-Bus.
+
server:main Connecting to the Session D-Bus.
-
server:main Registering the well-known name (org.maemo.Platdev_ex)
+
server:main Registering the well-known name (org.maemo.Platdev_ex)
-
server:main RequestName returned 1.
+
server:main RequestName returned 1.
-
server:main Creating one Value object.
+
server:main Creating one Value object.
-
server:value_object_class_init: Called
+
server:value_object_class_init: Called
-
server:value_object_class_init: Creating signals
+
server:value_object_class_init: Creating signals
-
server:value_object_class_init: Binding to GLib/D-Bus
+
server:value_object_class_init: Binding to GLib/D-Bus
-
server:value_object_class_init: Done
+
server:value_object_class_init: Done
-
server:value_object_init: Called
+
server:value_object_init: Called
-
server:main Registering it on the D-Bus.
+
server:main Registering it on the D-Bus.
-
server:main Ready to serve requests (daemonizing).
+
server:main Ready to serve requests (daemonizing).
-
server: Not daemonizing (built with NO_DAEMON-build define)
+
server: Not daemonizing (built with NO_DAEMON-build define)
-
[sbox-FREMANTLE_X86: ~/glib-dbus-async] &gt; time run-standalone.sh dbus-send \
+
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
-
  --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
+
--type=method_call --print-reply --dest=org.maemo.Platdev_ex \
-
  /GlobalValue org.maemo.Value.getvalue1
+
/GlobalValue org.maemo.Value.getvalue1
-
server:value_object_getvalue1: Called (internal value1 is 0)
+
server:value_object_getvalue1: Called (internal value1 is 0)
-
server:value_object_getvalue1: Delaying operation
+
server:value_object_getvalue1: Delaying operation
-
method return sender=:1.54 -&gt; dest=:1.56
+
method return sender=:1.54 -> dest=:1.56
-
  int32 0
+
int32 0
-
+
-
real    0m5.066s
+
-
user    0m0.004s
+
-
sys    0m0.056s
+
-
</div>
+
real    0m5.066s
 +
user    0m0.004s
 +
sys    0m0.056s
 +
</pre>
In the example above, the time shell built-in command was used. It runs the given command while measuring the wall clock time (the real time) and the time used while executing the code and system calls. In this case, only the real time is of any interest. The method call delays for about 5 seconds, as it should. The delay (even when given with microsecond resolution) is always approximate, and longer than the requested amount. Exact delay depends on many factors, most of which cannot be directly influenced.
In the example above, the time shell built-in command was used. It runs the given command while measuring the wall clock time (the real time) and the time used while executing the code and system calls. In this case, only the real time is of any interest. The method call delays for about 5 seconds, as it should. The delay (even when given with microsecond resolution) is always approximate, and longer than the requested amount. Exact delay depends on many factors, most of which cannot be directly influenced.
Line 66: Line 63:
The next experiment deals with a likely scenario, where another method call comes along while the first one is still being executed. This is best tested by just repeating the sending command twice, but running the first one on the background (so that the shell does not wait for it to complete first). The server is still running on the background from the previous test:
The next experiment deals with a likely scenario, where another method call comes along while the first one is still being executed. This is best tested by just repeating the sending command twice, but running the first one on the background (so that the shell does not wait for it to complete first). The server is still running on the background from the previous test:
-
<div class="graybox">
+
<pre>
-
[sbox-FREMANTLE_X86: ~/glib-dbus-async] &gt; time run-standalone.sh dbus-send \
+
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
-
  --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
+
--type=method_call --print-reply --dest=org.maemo.Platdev_ex \
-
  /GlobalValue org.maemo.Value.getvalue1 &amp;
+
/GlobalValue org.maemo.Value.getvalue1 &
-
[2] 17010
+
[2] 17010
-
server:value_object_getvalue1: Called (internal value1 is 0)
+
server:value_object_getvalue1: Called (internal value1 is 0)
-
server:value_object_getvalue1: Delaying operation
+
server:value_object_getvalue1: Delaying operation
-
[sbox-FREMANTLE_X86: ~/glib-dbus-async] &gt; time run-standalone.sh dbus-send \
+
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
-
  --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
+
--type=method_call --print-reply --dest=org.maemo.Platdev_ex \
-
  /GlobalValue org.maemo.Value.getvalue1
+
/GlobalValue org.maemo.Value.getvalue1
-
method return sender=:1.54 -&gt; dest=:1.57
+
method return sender=:1.54 -> dest=:1.57
-
  int32 0
+
  int32 0
-
   
+
-
real    0m5.176s
+
-
user    0m0.008s
+
-
sys    0m0.092s
+
-
server:value_object_getvalue1: Called (internal value1 is 0)
+
-
server:value_object_getvalue1: Delaying operation
+
-
method return sender=:1.54 -&gt; dest=:1.58
+
-
  int32 0
+
-
+
-
real    0m9.852s
+
-
user    0m0.004s
+
-
sys    0m0.052s
+
-
</div>
+
real    0m5.176s
 +
user    0m0.008s
 +
sys    0m0.092s
 +
server:value_object_getvalue1: Called (internal value1 is 0)
 +
server:value_object_getvalue1: Delaying operation
 +
method return sender=:1.54 -> dest=:1.58
 +
int32 0
 +
 
 +
real    0m9.852s
 +
user    0m0.004s
 +
sys    0m0.052s
 +
</pre>
What can be seen from the above output is that the first client is delayed for about 5 seconds, while the second client (which was launched shortly after the first) is already delayed by a much longer period. This is to be expected, as the server can only process one request at a time and delays each request by 5 seconds.
What can be seen from the above output is that the first client is delayed for about 5 seconds, while the second client (which was launched shortly after the first) is already delayed by a much longer period. This is to be expected, as the server can only process one request at a time and delays each request by 5 seconds.
Line 97: Line 93:
The clients must be able to continue their "normal work" while they wait for the response from the server. Since this is just example code, "normal work" for the clients is waiting for the response while blocking on incoming events (converted into callbacks). However, if the example programs are graphical, the asynchronous approach make it possible for them to react to user input. D-Bus by itself does not support cancellation of method calls, once processing has started on the server side, so adding cancellation support requires a separate method call to the server. Since the server only handles one operation at a time, the current server cannot support method call cancellations at all.
The clients must be able to continue their "normal work" while they wait for the response from the server. Since this is just example code, "normal work" for the clients is waiting for the response while blocking on incoming events (converted into callbacks). However, if the example programs are graphical, the asynchronous approach make it possible for them to react to user input. D-Bus by itself does not support cancellation of method calls, once processing has started on the server side, so adding cancellation support requires a separate method call to the server. Since the server only handles one operation at a time, the current server cannot support method call cancellations at all.
-
== Asynchronous Method Calls Using Stubs ==
+
== Asynchronous Method Calls Using Stubs ==
-
When the glib-bindings-tool is run, it already generates the necessary wrapping stubs to support launching asynchronous method calls. What is then left to do is implementing the callback functions correctly, processing the return errors and launching the method call. glib-dbus-async/client-stubs.c
+
When the <code>glib-bindings-tool</code> is run, it already generates the necessary wrapping stubs to support launching asynchronous method calls. What is then left to do is implementing the callback functions correctly, processing the return errors and launching the method call. glib-dbus-async/client-stubs.c
-
<tt><span>''<span><font color="#9A1900">/* Pull in the client stubs that were generated with</font></span>''</span>
+
<source lang="c">
-
<span>''<span><font color="#9A1900">  dbus-binding-tool */</font></span>''</span>
+
/* Pull in the client stubs that were generated with
-
<span>'''<span><font color="#000080"><nowiki>#include</nowiki></font></span>'''</span> <span><font color="#FF0000">"value-client-stub.h"</font></span></tt>
+
  dbus-binding-tool */
 +
#include "value-client-stub.h"
 +
</source>
-
The client has been simplified, so that it now only operates on value1. The callback that is called from the stub code is presented next: glib-dbus-async/client-stubs.c
+
The client has been simplified, so that it now only operates on <code>value1</code>. The callback that is called from the stub code is presented next: glib-dbus-async/client-stubs.c
-
<tt><span>''<span><font color="#9A1900">/**</font></span>''</span>
+
<source lang="c">
-
  <span>''<span><font color="#9A1900"> * This function is called when the async setvalue1 either</font></span>''</span>
+
/**
-
  <span>''<span><font color="#9A1900"> * completes, timeouts or fails (our server however does not signal</font></span>''</span>
+
  * This function is called when the async setvalue1 either
-
  <span>''<span><font color="#9A1900"> * errors, but the client D-Bus library can do that). When this example</font></span>''</span>
+
  * completes, timeouts or fails (our server however does not signal
-
  <span>''<span><font color="#9A1900"> * program is left running for a while, you can see all three cases.</font></span>''</span>
+
  * errors, but the client D-Bus library can do that). When this example
-
  <span>''<span><font color="#9A1900"> *</font></span>''</span>
+
  * program is left running for a while, you can see all three cases.
-
  <span>''<span><font color="#9A1900"> * The prototype must match the one generated by the dbus-binding-tool</font></span>''</span>
+
  *
-
  <span>''<span><font color="#9A1900"> * (org_maemo_Value_setvalue1_reply).</font></span>''</span>
+
  * The prototype must match the one generated by the dbus-binding-tool
-
  <span>''<span><font color="#9A1900"> *</font></span>''</span>
+
  * (org_maemo_Value_setvalue1_reply).
-
  <span>''<span><font color="#9A1900"> * Because there is no return value from the RPC, the only useful</font></span>''</span>
+
  *
-
  <span>''<span><font color="#9A1900"> * parameter that we get is the error object, which we check.</font></span>''</span>
+
  * Because there is no return value from the RPC, the only useful
-
  <span>''<span><font color="#9A1900"> * If error is NULL, that means no error. Otherwise the RPC call</font></span>''</span>
+
  * parameter that we get is the error object, which we check.
-
  <span>''<span><font color="#9A1900"> * failed and we should check what the cause was.</font></span>''</span>
+
  * If error is NULL, that means no error. Otherwise the RPC call
-
  <span>''<span><font color="#9A1900"> */</font></span>''</span>
+
  * failed and we should check what the cause was.
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span> <span><font color="#009900">void</font></span> <span>'''<span><font color="#000000">setValue1Completed</font></span>'''</span><span><font color="#990000">(</font></span>DBusGProxy<span><font color="#990000"><nowiki>*</nowiki></font></span> proxy<span><font color="#990000">,</font></span> GError <span><font color="#990000"><nowiki>*</nowiki></font></span>error<span><font color="#990000">,</font></span>
+
  */
-
                                                  gpointer userData<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
static void setValue1Completed(DBusGProxy* proxy, GError *error,
-
+
                                                  gpointer userData) {
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":%s:setValue1Completed</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">,</font></span> <span>'''<span><font color="#000000">timestamp</font></span>'''</span><span><font color="#990000">());</font></span>
+
 
-
  <span>'''<span><font color="#0000FF">if</font></span>'''</span> <span><font color="#990000">(</font></span>error <span><font color="#990000"><nowiki>!=</nowiki></font></span> NULL<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
  g_print(PROGNAME ":%s:setValue1Completed\n", timestamp());
-
    <span>'''<span><font color="#000000">g_printerr</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">"      ERROR: %s</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">,</font></span> error<span><font color="#990000">-&gt;</font></span>message<span><font color="#990000">);</font></span>
+
  if (error != NULL) {
-
    <span>''<span><font color="#9A1900">/* We need to release the error object because the stub code does</font></span>''</span>
+
    g_printerr(PROGNAME "      ERROR: %s\n", error->message);
-
<span>''<span><font color="#9A1900">      not do it automatically. */</font></span>''</span>
+
    /* We need to release the error object because the stub code does
-
    <span>'''<span><font color="#000000">g_error_free</font></span>'''</span><span><font color="#990000">(</font></span>error<span><font color="#990000">);</font></span>
+
      not do it automatically. */
-
  <span><font color="#FF0000">}</font></span> <span>'''<span><font color="#0000FF">else</font></span>'''</span> <span><font color="#FF0000">{</font></span>
+
    g_error_free(error);
-
    <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">"      SUCCESS</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">);</font></span>
+
  } else {
-
  <span><font color="#FF0000">}</font></span>
+
    g_print(PROGNAME "      SUCCESS\n");
-
<span><font color="#FF0000">}</font></span></tt>
+
  }
 +
}
 +
</source>
Because the method call does not return any data, the parameters for the callback are at minimum (those three are always received). Handling errors must be performed within the callback, because errors could be delayed from the server, and not visible immediately at launch time.
Because the method call does not return any data, the parameters for the callback are at minimum (those three are always received). Handling errors must be performed within the callback, because errors could be delayed from the server, and not visible immediately at launch time.
Line 139: Line 139:
Note that the callback does not terminate the program on errors. This is done on purpose in order to demonstrate some common asynchronous problems below. The timestamp function is a small utility function to return a pointer to a string, representing the number of seconds because the program started (useful to visualize the order of the different asynchronous events below). glib-dbus-async/client-stubs.c
Note that the callback does not terminate the program on errors. This is done on purpose in order to demonstrate some common asynchronous problems below. The timestamp function is a small utility function to return a pointer to a string, representing the number of seconds because the program started (useful to visualize the order of the different asynchronous events below). glib-dbus-async/client-stubs.c
-
<tt><span>''<span><font color="#9A1900">/**</font></span>''</span>
+
<source lang="c">
-
  <span>''<span><font color="#9A1900"> * This function is called repeatedly from within the mainloop</font></span>''</span>
+
/**
-
  <span>''<span><font color="#9A1900"> * timer launch code.</font></span>''</span>
+
  * This function is called repeatedly from within the mainloop
-
  <span>''<span><font color="#9A1900"> *</font></span>''</span>
+
  * timer launch code.
-
  <span>''<span><font color="#9A1900"> * It launches asynchronous RPC method to set value1 with ever</font></span>''</span>
+
  *
-
  <span>''<span><font color="#9A1900"> * increasing argument.</font></span>''</span>
+
  * It launches asynchronous RPC method to set value1 with ever
-
  <span>''<span><font color="#9A1900"> */</font></span>''</span>
+
  * increasing argument.
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span> gboolean <span>'''<span><font color="#000000">timerCallback</font></span>'''</span><span><font color="#990000">(</font></span>DBusGProxy<span><font color="#990000"><nowiki>*</nowiki></font></span> remoteobj<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
  */
-
+
static gboolean timerCallback(DBusGProxy* remoteobj) {
-
  <span>''<span><font color="#9A1900">/* Local value that we'll start updating to the remote object. */</font></span>''</span>
+
 
-
  <span>'''<span><font color="#0000FF">static</font></span>'''</span> gint localValue1 <span><font color="#990000"><nowiki>=</nowiki></font></span> <span><font color="#990000">-</font></span><span><font color="#993399">80</font></span><span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Local value that we'll start updating to the remote object. */
-
+
  static gint localValue1 = -80;
-
  <span>''<span><font color="#9A1900">/* Start the RPC.</font></span>''</span>
+
 
-
<span>''<span><font color="#9A1900">    This is done by calling the stub function that takes the new</font></span>''</span>
+
  /* Start the RPC.
-
<span>''<span><font color="#9A1900">    value and the callback function to call on reply getting back.</font></span>''</span>
+
    This is done by calling the stub function that takes the new
-
+
    value and the callback function to call on reply getting back.
-
<span>''<span><font color="#9A1900">    The stub returns a DBusGProxyCall object, but we do not need it</font></span>''</span>
+
 
-
<span>''<span><font color="#9A1900">    so we ignore the return value. The return value could be used</font></span>''</span>
+
    The stub returns a DBusGProxyCall object, but we do not need it
-
<span>''<span><font color="#9A1900">    to cancel a pending request (from client side) with</font></span>''</span>
+
    so we ignore the return value. The return value could be used
-
<span>''<span><font color="#9A1900">    dbus_g_proxy_cancel_call. We could also pass a pointer to</font></span>''</span>
+
    to cancel a pending request (from client side) with
-
<span>''<span><font color="#9A1900">    user-data (last parameter), but we do not need one in this example.</font></span>''</span>
+
    dbus_g_proxy_cancel_call. We could also pass a pointer to
-
<span>''<span><font color="#9A1900">    It is normally used to "carry around" the application state.</font></span>''</span>
+
    user-data (last parameter), but we do not need one in this example.
-
<span>''<span><font color="#9A1900">    */</font></span>''</span>
+
    It is normally used to "carry around" the application state.
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":%s:timerCallback launching setvalue1</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">,</font></span>
+
    */
-
          <span>'''<span><font color="#000000">timestamp</font></span>'''</span><span><font color="#990000">());</font></span>
+
  g_print(PROGNAME ":%s:timerCallback launching setvalue1\n",
-
  <span>'''<span><font color="#000000">org_maemo_Value_setvalue1_async</font></span>'''</span><span><font color="#990000">(</font></span>remoteobj<span><font color="#990000">,</font></span> localValue1<span><font color="#990000">,</font></span>
+
          timestamp());
-
                                  setValue1Completed<span><font color="#990000">,</font></span> NULL<span><font color="#990000">);</font></span>
+
  org_maemo_Value_setvalue1_async(remoteobj, localValue1,
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":%s:timerCallback setvalue1 launched</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">,</font></span>
+
                                  setValue1Completed, NULL);
-
          <span>'''<span><font color="#000000">timestamp</font></span>'''</span><span><font color="#990000">());</font></span>
+
  g_print(PROGNAME ":%s:timerCallback setvalue1 launched\n",
-
+
          timestamp());
-
  <span>''<span><font color="#9A1900">/* Step the local value forward. */</font></span>''</span>
+
 
-
  localValue1 <span><font color="#990000">+=</font></span> <span><font color="#993399">10</font></span><span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Step the local value forward. */
-
+
  localValue1 += 10;
-
  <span>''<span><font color="#9A1900">/* Repeat timer later. */</font></span>''</span>
+
 
-
  <span>'''<span><font color="#0000FF">return</font></span>'''</span> TRUE<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Repeat timer later. */
-
<span><font color="#FF0000">}</font></span></tt>
+
  return TRUE;
 +
}
 +
</source>
Using the stub code is rather simple. For each generated synchronous version of a method wrapper, there is also the a _async version of the call. The main difference with the parameters is the removal of the GError pointer (because errors are handled in the callback), and the addition of the callback function to use when the method completes, times out or encounters an error.
Using the stub code is rather simple. For each generated synchronous version of a method wrapper, there is also the a _async version of the call. The main difference with the parameters is the removal of the GError pointer (because errors are handled in the callback), and the addition of the callback function to use when the method completes, times out or encounters an error.
Line 184: Line 186:
When the simple test program is built and run, it can be seen that everything starts off quite well. But at some point, problems start to appear:
When the simple test program is built and run, it can be seen that everything starts off quite well. But at some point, problems start to appear:
-
<code>[sbox-FREMANTLE_X86: /glib-dbus-async] &gt; make client-stubs
+
<pre>
-
dbus-binding-tool --prefix=value_object --mode=glib-client \
+
[sbox-FREMANTLE_X86: /glib-dbus-async] > make client-stubs
-
  value-dbus-interface.xml &gt; value-client-stub.h
+
dbus-binding-tool --prefix=value_object --mode=glib-client \
-
cc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include \
+
  value-dbus-interface.xml > value-client-stub.h
-
  -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -g -Wall \
+
cc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include \
-
  -DG_DISABLE_DEPRECATED -DNO_DAEMON -DPROGNAME=\"client-stubs\" \
+
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -g -Wall \
-
  -c client-stubs.c -o client-stubs.o
+
-DG_DISABLE_DEPRECATED -DNO_DAEMON -DPROGNAME=\"client-stubs\" \
-
cc client-stubs.o -o client-stubs -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
+
-c client-stubs.c -o client-stubs.o
-
[sbox-FREMANTLE_X86: /glib-dbus-async] &gt; run-standalone.sh ./client-stubs
+
cc client-stubs.o -o client-stubs -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
-
client-stubs:main Connecting to Session D-Bus.
+
[sbox-FREMANTLE_X86: /glib-dbus-async] > run-standalone.sh ./client-stubs
-
client-stubs:main Creating a GLib proxy object for Value.
+
client-stubs:main Connecting to Session D-Bus.
-
client-stubs: 0.00:main Starting main loop (first timer in 1s).
+
client-stubs:main Creating a GLib proxy object for Value.
-
client-stubs: 1.00:timerCallback launching setvalue1
+
client-stubs: 0.00:main Starting main loop (first timer in 1s).
-
client-stubs: 1.00:timerCallback setvalue1 launched
+
client-stubs: 1.00:timerCallback launching setvalue1
-
server:value_object_setvalue1: Called (valueIn=-80)
+
client-stubs: 1.00:timerCallback setvalue1 launched
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=-80)
-
client-stubs: 2.00:timerCallback launching setvalue1
+
server:value_object_setvalue1: Delaying operation
-
client-stubs: 2.00:timerCallback setvalue1 launched
+
client-stubs: 2.00:timerCallback launching setvalue1
-
client-stubs: 3.01:timerCallback launching setvalue1
+
client-stubs: 2.00:timerCallback setvalue1 launched
-
client-stubs: 3.01:timerCallback setvalue1 launched
+
client-stubs: 3.01:timerCallback launching setvalue1
-
client-stubs: 4.01:timerCallback launching setvalue1
+
client-stubs: 3.01:timerCallback setvalue1 launched
-
client-stubs: 4.01:timerCallback setvalue1 launched
+
client-stubs: 4.01:timerCallback launching setvalue1
-
client-stubs: 5.02:timerCallback launching setvalue1
+
client-stubs: 4.01:timerCallback setvalue1 launched
-
client-stubs: 5.02:timerCallback setvalue1 launched
+
client-stubs: 5.02:timerCallback launching setvalue1
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
client-stubs: 5.02:timerCallback setvalue1 launched
-
server:value_object_setvalue1: Called (valueIn=-70)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=-70)
-
client-stubs: 6.01:setValue1Completed
+
server:value_object_setvalue1: Delaying operation
-
client-stubs      SUCCESS
+
client-stubs: 6.01:setValue1Completed
-
client-stubs: 6.02:timerCallback launching setvalue1
+
client-stubs      SUCCESS
-
client-stubs: 6.02:timerCallback setvalue1 launched
+
client-stubs: 6.02:timerCallback launching setvalue1
-
client-stubs: 7.02:timerCallback launching setvalue1
+
client-stubs: 6.02:timerCallback setvalue1 launched
-
client-stubs: 7.02:timerCallback setvalue1 launched
+
client-stubs: 7.02:timerCallback launching setvalue1
-
...
+
client-stubs: 7.02:timerCallback setvalue1 launched
-
client-stubs:25.04:timerCallback launching setvalue1
+
...
-
client-stubs:25.04:timerCallback setvalue1 launched
+
client-stubs:25.04:timerCallback launching setvalue1
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
client-stubs:25.04:timerCallback setvalue1 launched
-
server:value_object_setvalue1: Called (valueIn=-30)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=-30)
-
client-stubs:26.03:setValue1Completed
+
server:value_object_setvalue1: Delaying operation
-
client-stubs      SUCCESS
+
client-stubs:26.03:setValue1Completed
-
client-stubs:26.05:timerCallback launching setvalue1
+
client-stubs      SUCCESS
-
client-stubs:26.05:timerCallback setvalue1 launched
+
client-stubs:26.05:timerCallback launching setvalue1
-
client-stubs:27.05:timerCallback launching setvalue1
+
client-stubs:26.05:timerCallback setvalue1 launched
-
client-stubs:27.05:timerCallback setvalue1 launched
+
client-stubs:27.05:timerCallback launching setvalue1
-
client-stubs:28.05:timerCallback launching setvalue1
+
client-stubs:27.05:timerCallback setvalue1 launched
-
client-stubs:28.05:timerCallback setvalue1 launched
+
client-stubs:28.05:timerCallback launching setvalue1
-
client-stubs:29.05:timerCallback launching setvalue1
+
client-stubs:28.05:timerCallback setvalue1 launched
-
client-stubs:29.05:timerCallback setvalue1 launched
+
client-stubs:29.05:timerCallback launching setvalue1
-
client-stubs:30.05:timerCallback launching setvalue1
+
client-stubs:29.05:timerCallback setvalue1 launched
-
client-stubs:30.05:timerCallback setvalue1 launched
+
client-stubs:30.05:timerCallback launching setvalue1
-
client-stubs:31.02:setValue1Completed
+
client-stubs:30.05:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:31.02:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
broken.
-
server:value_object_setvalue1: Called (valueIn=-20)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=-20)
-
client-stubs:31.05:timerCallback launching setvalue1
+
server:value_object_setvalue1: Delaying operation
-
client-stubs:31.05:timerCallback setvalue1 launched
+
client-stubs:31.05:timerCallback launching setvalue1
-
client-stubs:32.03:setValue1Completed
+
client-stubs:31.05:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:32.03:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
client-stubs:32.05:timerCallback launching setvalue1
+
broken.
-
client-stubs:32.05:timerCallback setvalue1 launched
+
client-stubs:32.05:timerCallback launching setvalue1
-
client-stubs:33.03:setValue1Completed
+
client-stubs:32.05:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:33.03:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
client-stubs:33.05:timerCallback launching setvalue1
+
broken.
-
client-stubs:33.05:timerCallback setvalue1 launched
+
client-stubs:33.05:timerCallback launching setvalue1
-
client-stubs:34.03:setValue1Completed
+
client-stubs:33.05:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:34.03:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
client-stubs:34.06:timerCallback launching setvalue1
+
broken.
-
client-stubs:34.06:timerCallback setvalue1 launched
+
client-stubs:34.06:timerCallback launching setvalue1
-
client-stubs:35.03:setValue1Completed
+
client-stubs:34.06:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:35.03:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
client-stubs:35.05:timerCallback launching setvalue1
+
broken.
-
client-stubs:35.05:timerCallback setvalue1 launched
+
client-stubs:35.05:timerCallback launching setvalue1
-
client-stubs:36.04:setValue1Completed
+
client-stubs:35.05:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:36.04:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
broken.
-
server:value_object_setvalue1: Called (valueIn=-10)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=-10)
-
client-stubs:36.06:timerCallback launching setvalue1
+
server:value_object_setvalue1: Delaying operation
-
client-stubs:36.06:timerCallback setvalue1 launched
+
client-stubs:36.06:timerCallback launching setvalue1
-
client-stubs:37.04:setValue1Completed
+
client-stubs:36.06:timerCallback setvalue1 launched
-
client-stubs      ERROR: Did not receive a reply. Possible causes include:
+
client-stubs:37.04:setValue1Completed
-
  the remote application did not send a reply, the message bus security policy
+
client-stubs      ERROR: Did not receive a reply. Possible causes include:
-
  blocked the reply, the reply timeout expired, or the network connection was
+
the remote application did not send a reply, the message bus security policy
-
  broken.
+
blocked the reply, the reply timeout expired, or the network connection was
-
[Ctrl+c]
+
broken.
-
[sbox-FREMANTLE_X86: /glib-dbus-async] &gt;
+
[Ctrl+c]
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
[sbox-FREMANTLE_X86: /glib-dbus-async] >
-
server:value_object_setvalue1: Called (valueIn=30)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=30)
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
server:value_object_setvalue1: Delaying operation
-
server:value_object_setvalue1: Called (valueIn=40)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=40)
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
server:value_object_setvalue1: Delaying operation
-
server:value_object_setvalue1: Called (valueIn=50)
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
-
server:value_object_setvalue1: Delaying operation
+
server:value_object_setvalue1: Called (valueIn=50)
-
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
+
server:value_object_setvalue1: Delaying operation
-
...</code>
+
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
 +
...
 +
</pre>
What happens above is rather subtle. The timer callback in the client launches once per second and performs the RPC method launch. The server, however, still has the 5 second delay for each method call in it. The successive launches go on without any responses for a while. The first response comes back at about 6 seconds from the starting of the client. At this point, the server already has four other outstanding method calls that it has not handled. Slowly the method calls are accumulating at the server end and it does not deal with them quickly enough to satisfy the client.
What happens above is rather subtle. The timer callback in the client launches once per second and performs the RPC method launch. The server, however, still has the 5 second delay for each method call in it. The successive launches go on without any responses for a while. The first response comes back at about 6 seconds from the starting of the client. At this point, the server already has four other outstanding method calls that it has not handled. Slowly the method calls are accumulating at the server end and it does not deal with them quickly enough to satisfy the client.
-
After about 30 seconds, the setValue1Completed callback is invoked, but the method call fails. This has managed to trigger the method call timeout mechanism. After this point, all the method calls that have accumulated into the server (into a message queue) fail in the client, since they all return late now, even if the server actually does handle them.
+
After about 30 seconds, the <code>setValue1Completed</code> callback is invoked, but the method call fails. This has managed to trigger the method call timeout mechanism. After this point, all the method calls that have accumulated into the server (into a message queue) fail in the client, since they all return late now, even if the server actually does handle them.
Once the client is terminated, the server is still happily continuing serving the requests, oblivious to the fact that there is no client to process the responses.
Once the client is terminated, the server is still happily continuing serving the requests, oblivious to the fact that there is no client to process the responses.
Line 314: Line 318:
However, this fix is not complete, because the same problem manifests itself once there are multiple clients running in parallel and requesting the same methods. The proper fix is to make the server capable of serving multiple requests in parallel.
However, this fix is not complete, because the same problem manifests itself once there are multiple clients running in parallel and requesting the same methods. The proper fix is to make the server capable of serving multiple requests in parallel.
-
== Asynchronous Method Calls Using GLib Wrappers ==
+
== Asynchronous Method Calls Using GLib Wrappers ==
Sometimes the interface XML is missing, so the dbus-bindings-tool cannot be run to generate the stub code. The GLib wrappers are generic enough to enable building own method calls, when necessary.
Sometimes the interface XML is missing, so the dbus-bindings-tool cannot be run to generate the stub code. The GLib wrappers are generic enough to enable building own method calls, when necessary.
Line 320: Line 324:
Starting with some known generated stub code to see which parts can possibly be reused with some modifications is usually the easiest solution. This is shown in the last step of this example, in order to make a version of the asynchronous client that works without the stub generator.
Starting with some known generated stub code to see which parts can possibly be reused with some modifications is usually the easiest solution. This is shown in the last step of this example, in order to make a version of the asynchronous client that works without the stub generator.
-
The first step is to take a peek at the stub-generated code for the setvalue1 call (when used asynchronously): glib-dbus-async/value-client-stub.h
+
The first step is to take a peek at the stub-generated code for the <code>setvalue1</code> call (when used asynchronously): glib-dbus-async/value-client-stub.h
-
<tt><span>'''<span><font color="#0000FF">typedef</font></span>'''</span> <span><font color="#009900">void</font></span> <span><font color="#990000">(*</font></span>org_maemo_Value_setvalue1_reply<span><font color="#990000">)</font></span> <span><font color="#990000">(</font></span>DBusGProxy <span><font color="#990000"><nowiki>*</nowiki></font></span>proxy<span><font color="#990000">,</font></span>
+
<source lang="c">
-
                                                  GError <span><font color="#990000"><nowiki>*</nowiki></font></span>error<span><font color="#990000">,</font></span>
+
typedef void (*org_maemo_Value_setvalue1_reply) (DBusGProxy *proxy,
-
                                                  gpointer userdata<span><font color="#990000">);</font></span>
+
                                                GError *error,
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span> <span><font color="#009900">void</font></span>
+
                                                gpointer userdata);
-
<span>'''<span><font color="#000000">org_maemo_Value_setvalue1_async_callback</font></span>'''</span> <span><font color="#990000">(</font></span>DBusGProxy <span><font color="#990000"><nowiki>*</nowiki></font></span>proxy<span><font color="#990000">,</font></span>
+
static void
-
                                          DBusGProxyCall <span><font color="#990000"><nowiki>*</nowiki></font></span>call<span><font color="#990000">,</font></span>
+
org_maemo_Value_setvalue1_async_callback (DBusGProxy *proxy,
-
                                          <span><font color="#009900">void</font></span> <span><font color="#990000"><nowiki>*</nowiki></font></span>user_data<span><font color="#990000">)</font></span>
+
                                          DBusGProxyCall *call,
-
<span><font color="#FF0000">{</font></span>
+
                                          void *user_data)
-
  DBusGAsyncData <span><font color="#990000"><nowiki>*</nowiki></font></span>data <span><font color="#990000"><nowiki>=</nowiki></font></span> user_data<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
{
-
  GError <span><font color="#990000"><nowiki>*</nowiki></font></span>error <span><font color="#990000"><nowiki>=</nowiki></font></span> NULL<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  DBusGAsyncData *data = user_data;
-
  <span>'''<span><font color="#000000">dbus_g_proxy_end_call</font></span>'''</span> <span><font color="#990000">(</font></span>proxy<span><font color="#990000">,</font></span> call<span><font color="#990000">,</font></span> <span><font color="#990000">&amp;</font></span>error<span><font color="#990000">,</font></span> G_TYPE_INVALID<span><font color="#990000">);</font></span>
+
  GError *error = NULL;
-
  <span><font color="#990000">(*(</font></span>org_maemo_Value_setvalue1_reply<span><font color="#990000">)</font></span>data<span><font color="#990000">-&gt;</font></span>cb<span><font color="#990000">)</font></span> <span><font color="#990000">(</font></span>proxy<span><font color="#990000">,</font></span> error<span><font color="#990000">,</font></span>
+
  dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID);
-
                                                data<span><font color="#990000">-&gt;</font></span>userdata<span><font color="#990000">);</font></span>
+
  (*(org_maemo_Value_setvalue1_reply)data->cb) (proxy, error,
-
  <span>'''<span><font color="#0000FF">return</font></span>'''</span><span><font color="#990000"><nowiki>;</nowiki></font></span>
+
                                                data->userdata);
-
<span><font color="#FF0000">}</font></span>
+
  return;
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span>
+
}
-
<span>'''<span><font color="#000080"><nowiki>#ifdef</nowiki></font></span>'''</span> G_HAVE_INLINE
+
static
-
inline
+
#ifdef G_HAVE_INLINE
-
<span>'''<span><font color="#000080"><nowiki>#endif</nowiki></font></span>'''</span>
+
inline
-
DBusGProxyCall<span><font color="#990000"><nowiki>*</nowiki></font></span>
+
#endif
-
<span>'''<span><font color="#000000">org_maemo_Value_setvalue1_async</font></span>'''</span> <span><font color="#990000">(</font></span>DBusGProxy <span><font color="#990000"><nowiki>*</nowiki></font></span>proxy<span><font color="#990000">,</font></span>
+
DBusGProxyCall*
-
                                  <span>'''<span><font color="#0000FF">const</font></span>'''</span> gint IN_new_value<span><font color="#990000">,</font></span>
+
org_maemo_Value_setvalue1_async (DBusGProxy *proxy,
-
                              org_maemo_Value_setvalue1_reply callback<span><font color="#990000">,</font></span>
+
                                const gint IN_new_value,
-
                                  gpointer userdata<span><font color="#990000">)</font></span>
+
                            org_maemo_Value_setvalue1_reply callback,
-
<span><font color="#FF0000">{</font></span>
+
                                gpointer userdata)
-
  DBusGAsyncData <span><font color="#990000"><nowiki>*</nowiki></font></span>stuff<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
{
-
  stuff <span><font color="#990000"><nowiki>=</nowiki></font></span> <span>'''<span><font color="#000000">g_new</font></span>'''</span> <span><font color="#990000">(</font></span>DBusGAsyncData<span><font color="#990000">,</font></span> <span><font color="#993399">1</font></span><span><font color="#990000">);</font></span>
+
  DBusGAsyncData *stuff;
-
  stuff<span><font color="#990000">-&gt;</font></span>cb <span><font color="#990000"><nowiki>=</nowiki></font></span> <span>'''<span><font color="#000000">G_CALLBACK</font></span>'''</span> <span><font color="#990000">(</font></span>callback<span><font color="#990000">);</font></span>
+
  stuff = g_new (DBusGAsyncData, 1);
-
  stuff<span><font color="#990000">-&gt;</font></span>userdata <span><font color="#990000"><nowiki>=</nowiki></font></span> userdata<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  stuff->cb = G_CALLBACK (callback);
-
  <span>'''<span><font color="#0000FF">return</font></span>'''</span> <span>'''<span><font color="#000000">dbus_g_proxy_begin_call</font></span>'''</span> <span><font color="#990000">(</font></span>
+
  stuff->userdata = userdata;
-
    proxy<span><font color="#990000">,</font></span> <span><font color="#FF0000">"setvalue1"</font></span><span><font color="#990000">,</font></span> org_maemo_Value_setvalue1_async_callback<span><font color="#990000">,</font></span>
+
  return dbus_g_proxy_begin_call (
-
    stuff<span><font color="#990000">,</font></span> g_free<span><font color="#990000">,</font></span> G_TYPE_INT<span><font color="#990000">,</font></span> IN_new_value<span><font color="#990000">,</font></span> G_TYPE_INVALID<span><font color="#990000">);</font></span>
+
    proxy, "setvalue1", org_maemo_Value_setvalue1_async_callback,
-
<span><font color="#FF0000">}</font></span></tt>
+
    stuff, g_free, G_TYPE_INT, IN_new_value, G_TYPE_INVALID);
 +
}
 +
</source>
What is notable in the code snippet above is that the _async method creates a temporary small structure that holds the pointer to the callback function and a copy of the userdata pointer. This small structure is then passed to dbus_g_proxy_begin_call, along with the address of the generated callback wrapper function (org_maemo_Value_setvalue1_async_callback). The GLib async launcher also takes a function pointer to a function to be used when the supplied "user-data" (in this case, the small structure) needs to be disposed of after the call. Because it uses g_new to allocate the small structure, it passes g_free as the freeing function. Next comes the argument specification for the method call, which obeys the same rules as the LibOSSO ones.
What is notable in the code snippet above is that the _async method creates a temporary small structure that holds the pointer to the callback function and a copy of the userdata pointer. This small structure is then passed to dbus_g_proxy_begin_call, along with the address of the generated callback wrapper function (org_maemo_Value_setvalue1_async_callback). The GLib async launcher also takes a function pointer to a function to be used when the supplied "user-data" (in this case, the small structure) needs to be disposed of after the call. Because it uses g_new to allocate the small structure, it passes g_free as the freeing function. Next comes the argument specification for the method call, which obeys the same rules as the LibOSSO ones.
Line 364: Line 370:
The first step for that is to implement the RPC asynchronous launch code: glib-dbus-async/client-glib.c
The first step for that is to implement the RPC asynchronous launch code: glib-dbus-async/client-glib.c
-
<tt><span>''<span><font color="#9A1900">/**</font></span>''</span>
+
<source lang="c">
-
  <span>''<span><font color="#9A1900"> * This function will be called repeatedly from within the mainloop</font></span>''</span>
+
/**
-
  <span>''<span><font color="#9A1900"> * timer launch code.</font></span>''</span>
+
  * This function will be called repeatedly from within the mainloop
-
  <span>''<span><font color="#9A1900"> *</font></span>''</span>
+
  * timer launch code.
-
  <span>''<span><font color="#9A1900"> * It will launch asynchronous RPC method to set value1 with ever</font></span>''</span>
+
  *
-
  <span>''<span><font color="#9A1900"> * increasing argument.</font></span>''</span>
+
  * It will launch asynchronous RPC method to set value1 with ever
-
  <span>''<span><font color="#9A1900"> */</font></span>''</span>
+
  * increasing argument.
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span> gboolean <span>'''<span><font color="#000000">timerCallback</font></span>'''</span><span><font color="#990000">(</font></span>DBusGProxy<span><font color="#990000"><nowiki>*</nowiki></font></span> remoteobj<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
  */
-
+
static gboolean timerCallback(DBusGProxy* remoteobj) {
-
  <span>''<span><font color="#9A1900">/* Local value that we'll start updating to the remote object. */</font></span>''</span>
+
 
-
  <span>'''<span><font color="#0000FF">static</font></span>'''</span> gint localValue1 <span><font color="#990000"><nowiki>=</nowiki></font></span> <span><font color="#990000">-</font></span><span><font color="#993399">80</font></span><span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Local value that we'll start updating to the remote object. */
-
+
  static gint localValue1 = -80;
-
  <span>''<span><font color="#9A1900">/* Start the first RPC.</font></span>''</span>
+
 
-
<span>''<span><font color="#9A1900">    The call using GLib/D-Bus is only slightly more complex than the</font></span>''</span>
+
  /* Start the first RPC.
-
<span>''<span><font color="#9A1900">    stubs. The overall operation is the same. */</font></span>''</span>
+
    The call using GLib/D-Bus is only slightly more complex than the
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":timerCallback launching setvalue1</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">);</font></span>
+
    stubs. The overall operation is the same. */
-
  <span>'''<span><font color="#000000">dbus_g_proxy_begin_call</font></span>'''</span><span><font color="#990000">(</font></span>remoteobj<span><font color="#990000">,</font></span>
+
  g_print(PROGNAME ":timerCallback launching setvalue1\n");
-
                          <span>''<span><font color="#9A1900">/* Method name. */</font></span>''</span>
+
  dbus_g_proxy_begin_call(remoteobj,
-
                          <span><font color="#FF0000">"setvalue1"</font></span><span><font color="#990000">,</font></span>
+
                          /* Method name. */
-
                          <span>''<span><font color="#9A1900">/* Callback to call on "completion". */</font></span>''</span>
+
                          "setvalue1",
-
                          setValue1Completed<span><font color="#990000">,</font></span>
+
                          /* Callback to call on "completion". */
-
                          <span>''<span><font color="#9A1900">/* User-data to pass to callback. */</font></span>''</span>
+
                          setValue1Completed,
-
                          NULL<span><font color="#990000">,</font></span>
+
                          /* User-data to pass to callback. */
-
                          <span>''<span><font color="#9A1900">/* Function to call to free userData after</font></span>''</span>
+
                          NULL,
-
<span>''<span><font color="#9A1900">                            callback returns. */</font></span>''</span>
+
                          /* Function to call to free userData after
-
                          NULL<span><font color="#990000">,</font></span>
+
                            callback returns. */
-
                          <span>''<span><font color="#9A1900">/* First argument GType. */</font></span>''</span>
+
                          NULL,
-
                          G_TYPE_INT<span><font color="#990000">,</font></span>
+
                          /* First argument GType. */
-
                          <span>''<span><font color="#9A1900">/* First argument value (passed by value) */</font></span>''</span>
+
                          G_TYPE_INT,
-
                          localValue1<span><font color="#990000">,</font></span>
+
                          /* First argument value (passed by value) */
-
                          <span>''<span><font color="#9A1900">/* Terminate argument list. */</font></span>''</span>
+
                          localValue1,
-
                          G_TYPE_INVALID<span><font color="#990000">);</font></span>
+
                          /* Terminate argument list. */
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":timerCallback setvalue1 launched</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">);</font></span>
+
                          G_TYPE_INVALID);
-
+
  g_print(PROGNAME ":timerCallback setvalue1 launched\n");
-
  <span>''<span><font color="#9A1900">/* Step the local value forward. */</font></span>''</span>
+
 
-
  localValue1 <span><font color="#990000">+=</font></span> <span><font color="#993399">10</font></span><span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Step the local value forward. */
-
+
  localValue1 += 10;
-
  <span>''<span><font color="#9A1900">/* Repeat timer later. */</font></span>''</span>
+
 
-
  <span>'''<span><font color="#0000FF">return</font></span>'''</span> TRUE<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* Repeat timer later. */
-
<span><font color="#FF0000">}</font></span></tt>
+
  return TRUE;
 +
}
 +
</source>
And the callback that is invoked on method call completion timeouts or errors: glib-dbus-async/client-glib.c
And the callback that is invoked on method call completion timeouts or errors: glib-dbus-async/client-glib.c
-
<tt><span>''<span><font color="#9A1900">/**</font></span>''</span>
+
<source lang="c">
-
  <span>''<span><font color="#9A1900"> * This function is called when the async setvalue1 either</font></span>''</span>
+
/**
-
  <span>''<span><font color="#9A1900"> * completes, timeouts or fails (same as before). The main difference in</font></span>''</span>
+
  * This function is called when the async setvalue1 either
-
  <span>''<span><font color="#9A1900"> * using GLib/D-Bus wrappers is that we need to "collect" the return</font></span>''</span>
+
  * completes, timeouts or fails (same as before). The main difference in
-
  <span>''<span><font color="#9A1900"> * value (or error). This is done with the _end_call function.</font></span>''</span>
+
  * using GLib/D-Bus wrappers is that we need to "collect" the return
-
  <span>''<span><font color="#9A1900"> *</font></span>''</span>
+
  * value (or error). This is done with the _end_call function.
-
  <span>''<span><font color="#9A1900"> * Note that all callbacks that are to be registered for RPC async</font></span>''</span>
+
  *
-
  <span>''<span><font color="#9A1900"> * notifications using dbus_g_proxy_begin_call must follow the</font></span>''</span>
+
  * Note that all callbacks that are to be registered for RPC async
-
  <span>''<span><font color="#9A1900"> * following prototype: DBusGProxyCallNotify .</font></span>''</span>
+
  * notifications using dbus_g_proxy_begin_call must follow the
-
  <span>''<span><font color="#9A1900"> */</font></span>''</span>
+
  * following prototype: DBusGProxyCallNotify .
-
<span>'''<span><font color="#0000FF">static</font></span>'''</span> <span><font color="#009900">void</font></span> <span>'''<span><font color="#000000">setValue1Completed</font></span>'''</span><span><font color="#990000">(</font></span>DBusGProxy<span><font color="#990000"><nowiki>*</nowiki></font></span> proxy<span><font color="#990000">,</font></span>
+
  */
-
                                DBusGProxyCall<span><font color="#990000"><nowiki>*</nowiki></font></span> call<span><font color="#990000">,</font></span>
+
static void setValue1Completed(DBusGProxy* proxy,
-
                                gpointer userData<span><font color="#990000">)</font></span> <span><font color="#FF0000">{</font></span>
+
                              DBusGProxyCall* call,
-
+
                              gpointer userData) {
-
  <span>''<span><font color="#9A1900">/* This will hold the GError object (if any). */</font></span>''</span>
+
 
-
  GError<span><font color="#990000"><nowiki>*</nowiki></font></span> error <span><font color="#990000"><nowiki>=</nowiki></font></span> NULL<span><font color="#990000"><nowiki>;</nowiki></font></span>
+
  /* This will hold the GError object (if any). */
-
+
  GError* error = NULL;
-
  <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">":setValue1Completed</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">);</font></span>
+
 
-
+
  g_print(PROGNAME ":setValue1Completed\n");
-
  <span>''<span><font color="#9A1900">/* We next need to collect the results from the RPC call.</font></span>''</span>
+
 
-
<span>''<span><font color="#9A1900">    The function returns FALSE on errors (which we check), although</font></span>''</span>
+
  /* We next need to collect the results from the RPC call.
-
<span>''<span><font color="#9A1900">    we could also check whether error-ptr is still NULL. */</font></span>''</span>
+
    The function returns FALSE on errors (which we check), although
-
  <span>'''<span><font color="#0000FF">if</font></span>'''</span> <span><font color="#990000">(!</font></span><span>'''<span><font color="#000000">dbus_g_proxy_end_call</font></span>'''</span><span><font color="#990000">(</font></span>proxy<span><font color="#990000">,</font></span>
+
    we could also check whether error-ptr is still NULL. */
-
                              <span>''<span><font color="#9A1900">/* The call that we're collecting. */</font></span>''</span>
+
  if (!dbus_g_proxy_end_call(proxy,
-
                              call<span><font color="#990000">,</font></span>
+
                            /* The call that we're collecting. */
-
                              <span>''<span><font color="#9A1900">/* Where to store the error (if any). */</font></span>''</span>
+
                            call,
-
                              <span><font color="#990000">&amp;</font></span>error<span><font color="#990000">,</font></span>
+
                            /* Where to store the error (if any). */
-
                              <span>''<span><font color="#9A1900">/* Next we list the GType codes for all</font></span>''</span>
+
                            &error,
-
<span>''<span><font color="#9A1900">                                the arguments we expect back. In our</font></span>''</span>
+
                            /* Next we list the GType codes for all
-
<span>''<span><font color="#9A1900">                                case there are none, so set to</font></span>''</span>
+
                                the arguments we expect back. In our
-
<span>''<span><font color="#9A1900">                                invalid. */</font></span>''</span>
+
                                case there are none, so set to
-
                              G_TYPE_INVALID<span><font color="#990000">))</font></span> <span><font color="#FF0000">{</font></span>
+
                                invalid. */
-
    <span>''<span><font color="#9A1900">/* Some error occurred while collecting the result. */</font></span>''</span>
+
                            G_TYPE_INVALID)) {
-
    <span>'''<span><font color="#000000">g_printerr</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">" ERROR: %s</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">,</font></span> error<span><font color="#990000">-&gt;</font></span>message<span><font color="#990000">);</font></span>
+
    /* Some error occurred while collecting the result. */
-
    <span>'''<span><font color="#000000">g_error_free</font></span>'''</span><span><font color="#990000">(</font></span>error<span><font color="#990000">);</font></span>
+
    g_printerr(PROGNAME " ERROR: %s\n", error->message);
-
  <span><font color="#FF0000">}</font></span> <span>'''<span><font color="#0000FF">else</font></span>'''</span> <span><font color="#FF0000">{</font></span>
+
    g_error_free(error);
-
    <span>'''<span><font color="#000000">g_print</font></span>'''</span><span><font color="#990000">(</font></span>PROGNAME <span><font color="#FF0000">" SUCCESS</font></span><span><font color="#CC33CC">\n</font></span><span><font color="#FF0000">"</font></span><span><font color="#990000">);</font></span>
+
  } else {
-
  <span><font color="#FF0000">}</font></span>
+
    g_print(PROGNAME " SUCCESS\n");
-
<span><font color="#FF0000">}</font></span></tt>
+
  }
 +
}
 +
</source>
The generated stub code is no longer needed, so the dependency rules for the stubless GLib version are different: glib-dbus-async/Makefile
The generated stub code is no longer needed, so the dependency rules for the stubless GLib version are different: glib-dbus-async/Makefile
-
<tt>client-glib<span><font color="#990000"><nowiki>:</nowiki></font></span> client-glib<span><font color="#990000">.</font></span>o
+
<source lang="make">
-
        <span><font color="#009900">$(CC)</font></span> <span><font color="#009900">$^</font></span> -o <span><font color="#009900">$@</font></span> <span><font color="#009900">$(LDFLAGS)</font></span>
+
client-glib: client-glib.o
-
<span>''<span><font color="#9A1900"><nowiki># Note that the GLib client doesn't need the stub code.</nowiki></font></span>''</span>
+
        $(CC) $^ -o $@ $(LDFLAGS)
-
client-glib<span><font color="#990000">.</font></span>o<span><font color="#990000"><nowiki>:</nowiki></font></span> client-glib<span><font color="#990000">.</font></span>c common-defs<span><font color="#990000">.</font></span>h
+
# Note that the GLib client doesn't need the stub code.
-
        <span><font color="#009900">$(CC)</font></span> <span><font color="#009900">$(CFLAGS)</font></span> -DPROGNAME<span><font color="#990000"><nowiki>=</nowiki></font></span>\"<span><font color="#009900">$(</font></span>basename <span><font color="#009900">$@</font></span><span><font color="#990000">)</font></span>\" -c <span><font color="#009900">$&lt;</font></span> -o <span><font color="#009900">$@</font></span>
+
client-glib.o: client-glib.c common-defs.h
-
</tt>
+
        $(CC) $(CFLAGS) -DPROGNAME=\"$(basename $@)\" -c $< -o $@
 +
</source>
-
Since the example program logic has not changed from the previous version, testing client-glib is not presented here (it can of course be tested if so desired, because the source code contains the fully working program). This version of the client also launches the method calls without waiting for previous method calls to complete.
+
Since the example program logic has not changed from the previous version, testing <code>client-glib</code> is not presented here (it can of course be tested if so desired, because the source code contains the fully working program). This version of the client also launches the method calls without waiting for previous method calls to complete.
[[Category:Development]]
[[Category:Development]]
[[Category:Documentation]]
[[Category:Documentation]]
[[Category:Fremantle]]
[[Category:Fremantle]]

Revision as of 10:27, 22 July 2010

The example code used below can be found in the garage svn.

Contents

Asynchronicity in D-Bus clients

So far all the RPC method calls that have been implemented here have been "fast", meaning that their execution has not depended on an access to slow services or external resources. In real life, however, it is quite likely that some services cannot be provided immediately, but have to wait for some external service to complete, before completing the method call.

The GLib wrappers provide a version of making method calls, where the call is launched (almost) immediately, and a callback is executed when the method call returns (either with a return value or an error).

Using the asynchronous wrappers is important when the program needs to update some kind of status or be reactive to the user (via a GUI or other interface). Otherwise, the program would block waiting for the RPC method to return, and would not be able to refresh the GUI or screen when required. An alternative solution is to use separate threads that run the synchronous methods. However, if this solution is used, synchronization between threads can become an issue, and debugging threaded programs is much harder than debugging single-threaded ones. Furthermore, the implementation of threads might be suboptimal in some environments. For these reasons, the thread scenario is not covered in this guide.

Slow running RPC methods are simulated here by adding a delay into the server method implementations, so that it becomes clear why asynchronous RPC mechanisms are important. As signals, by their nature, are asynchronous as well, they do not add anything to this example now. In order to simplify the code listings, the signal support from the asynchronous clients is dropped here, even though the server still contains them and emits them.

Slow Test Server

The only change on the server side is the addition of delays into each of the RPC methods (setvalue1, setvalue2, getvalue1 and getvalue2). This delay is added to the start of each function as follows: glib-dbus-async/server.c

/* How many microseconds to delay between each client operation. */
#define SERVER_DELAY_USEC (5*1000000UL)
  /*... Listing cut for brevity ...*/
gboolean value_object_setvalue1(ValueObject* obj, gint valueIn,
                                                  GError** error) {
  dbg("Called (valueIn=%d)", valueIn);
  g_assert(obj != NULL);
  dbg("Delaying operation");
  g_usleep(SERVER_DELAY_USEC);
  /* Compare the current value against old one. If they are the same,
     we do not need to do anything (except return success). */
  if (obj->value1 != valueIn) {

Building the server is done as before, but we notice the delay when we call an RPC method:

[sbox-FREMANTLE_X86: ~/glib-dbus-async] > run-standalone.sh ./server &
server:main Connecting to the Session D-Bus.
server:main Registering the well-known name (org.maemo.Platdev_ex)
server:main RequestName returned 1.
server:main Creating one Value object.
server:value_object_class_init: Called
server:value_object_class_init: Creating signals
server:value_object_class_init: Binding to GLib/D-Bus
server:value_object_class_init: Done
server:value_object_init: Called
server:main Registering it on the D-Bus.
server:main Ready to serve requests (daemonizing).
server: Not daemonizing (built with NO_DAEMON-build define)
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
 --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
 /GlobalValue org.maemo.Value.getvalue1
server:value_object_getvalue1: Called (internal value1 is 0)
server:value_object_getvalue1: Delaying operation
method return sender=:1.54 -> dest=:1.56
 int32 0

real    0m5.066s
user    0m0.004s
sys     0m0.056s

In the example above, the time shell built-in command was used. It runs the given command while measuring the wall clock time (the real time) and the time used while executing the code and system calls. In this case, only the real time is of any interest. The method call delays for about 5 seconds, as it should. The delay (even when given with microsecond resolution) is always approximate, and longer than the requested amount. Exact delay depends on many factors, most of which cannot be directly influenced.

The next experiment deals with a likely scenario, where another method call comes along while the first one is still being executed. This is best tested by just repeating the sending command twice, but running the first one on the background (so that the shell does not wait for it to complete first). The server is still running on the background from the previous test:

[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
 --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
 /GlobalValue org.maemo.Value.getvalue1 &
[2] 17010
server:value_object_getvalue1: Called (internal value1 is 0)
server:value_object_getvalue1: Delaying operation
[sbox-FREMANTLE_X86: ~/glib-dbus-async] > time run-standalone.sh dbus-send \
 --type=method_call --print-reply --dest=org.maemo.Platdev_ex \
 /GlobalValue org.maemo.Value.getvalue1
method return sender=:1.54 -> dest=:1.57
 int32 0

real    0m5.176s
user    0m0.008s
sys     0m0.092s
server:value_object_getvalue1: Called (internal value1 is 0)
server:value_object_getvalue1: Delaying operation
method return sender=:1.54 -> dest=:1.58
 int32 0

real    0m9.852s
user    0m0.004s
sys     0m0.052s

What can be seen from the above output is that the first client is delayed for about 5 seconds, while the second client (which was launched shortly after the first) is already delayed by a much longer period. This is to be expected, as the server can only process one request at a time and delays each request by 5 seconds.

The clients must be able to continue their "normal work" while they wait for the response from the server. Since this is just example code, "normal work" for the clients is waiting for the response while blocking on incoming events (converted into callbacks). However, if the example programs are graphical, the asynchronous approach make it possible for them to react to user input. D-Bus by itself does not support cancellation of method calls, once processing has started on the server side, so adding cancellation support requires a separate method call to the server. Since the server only handles one operation at a time, the current server cannot support method call cancellations at all.

Asynchronous Method Calls Using Stubs

When the glib-bindings-tool is run, it already generates the necessary wrapping stubs to support launching asynchronous method calls. What is then left to do is implementing the callback functions correctly, processing the return errors and launching the method call. glib-dbus-async/client-stubs.c

/* Pull in the client stubs that were generated with
   dbus-binding-tool */
#include "value-client-stub.h"

The client has been simplified, so that it now only operates on value1. The callback that is called from the stub code is presented next: glib-dbus-async/client-stubs.c

/**
 * This function is called when the async setvalue1 either
 * completes, timeouts or fails (our server however does not signal
 * errors, but the client D-Bus library can do that). When this example
 * program is left running for a while, you can see all three cases.
 *
 * The prototype must match the one generated by the dbus-binding-tool
 * (org_maemo_Value_setvalue1_reply).
 *
 * Because there is no return value from the RPC, the only useful
 * parameter that we get is the error object, which we check.
 * If error is NULL, that means no error. Otherwise the RPC call
 * failed and we should check what the cause was.
 */
static void setValue1Completed(DBusGProxy* proxy, GError *error,
                                                  gpointer userData) {
 
  g_print(PROGNAME ":%s:setValue1Completed\n", timestamp());
  if (error != NULL) {
    g_printerr(PROGNAME "       ERROR: %s\n", error->message);
    /* We need to release the error object because the stub code does
       not do it automatically. */
    g_error_free(error);
  } else {
    g_print(PROGNAME "       SUCCESS\n");
  }
}

Because the method call does not return any data, the parameters for the callback are at minimum (those three are always received). Handling errors must be performed within the callback, because errors could be delayed from the server, and not visible immediately at launch time.

Note that the callback does not terminate the program on errors. This is done on purpose in order to demonstrate some common asynchronous problems below. The timestamp function is a small utility function to return a pointer to a string, representing the number of seconds because the program started (useful to visualize the order of the different asynchronous events below). glib-dbus-async/client-stubs.c

/**
 * This function is called repeatedly from within the mainloop
 * timer launch code.
 *
 * It launches asynchronous RPC method to set value1 with ever
 * increasing argument.
 */
static gboolean timerCallback(DBusGProxy* remoteobj) {
 
  /* Local value that we'll start updating to the remote object. */
  static gint localValue1 = -80;
 
  /* Start the RPC.
     This is done by calling the stub function that takes the new
     value and the callback function to call on reply getting back.
 
     The stub returns a DBusGProxyCall object, but we do not need it
     so we ignore the return value. The return value could be used
     to cancel a pending request (from client side) with
     dbus_g_proxy_cancel_call. We could also pass a pointer to
     user-data (last parameter), but we do not need one in this example.
     It is normally used to "carry around" the application state.
     */
  g_print(PROGNAME ":%s:timerCallback launching setvalue1\n",
          timestamp());
  org_maemo_Value_setvalue1_async(remoteobj, localValue1,
                                  setValue1Completed, NULL);
  g_print(PROGNAME ":%s:timerCallback setvalue1 launched\n",
          timestamp());
 
  /* Step the local value forward. */
  localValue1 += 10;
 
  /* Repeat timer later. */
  return TRUE;
}

Using the stub code is rather simple. For each generated synchronous version of a method wrapper, there is also the a _async version of the call. The main difference with the parameters is the removal of the GError pointer (because errors are handled in the callback), and the addition of the callback function to use when the method completes, times out or encounters an error.

The main function remains the same from the previous client examples (a once-per-second timer is created and run from the mainloop until the program is terminated).

Problems with Asynchronicity

When the simple test program is built and run, it can be seen that everything starts off quite well. But at some point, problems start to appear:

[sbox-FREMANTLE_X86: /glib-dbus-async] > make client-stubs
dbus-binding-tool --prefix=value_object --mode=glib-client \
  value-dbus-interface.xml > value-client-stub.h
cc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include \
 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -g -Wall \
 -DG_DISABLE_DEPRECATED -DNO_DAEMON -DPROGNAME=\"client-stubs\" \
 -c client-stubs.c -o client-stubs.o
cc client-stubs.o -o client-stubs -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0
[sbox-FREMANTLE_X86: /glib-dbus-async] > run-standalone.sh ./client-stubs
client-stubs:main Connecting to Session D-Bus.
client-stubs:main Creating a GLib proxy object for Value.
client-stubs: 0.00:main Starting main loop (first timer in 1s).
client-stubs: 1.00:timerCallback launching setvalue1
client-stubs: 1.00:timerCallback setvalue1 launched
server:value_object_setvalue1: Called (valueIn=-80)
server:value_object_setvalue1: Delaying operation
client-stubs: 2.00:timerCallback launching setvalue1
client-stubs: 2.00:timerCallback setvalue1 launched
client-stubs: 3.01:timerCallback launching setvalue1
client-stubs: 3.01:timerCallback setvalue1 launched
client-stubs: 4.01:timerCallback launching setvalue1
client-stubs: 4.01:timerCallback setvalue1 launched
client-stubs: 5.02:timerCallback launching setvalue1
client-stubs: 5.02:timerCallback setvalue1 launched
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=-70)
server:value_object_setvalue1: Delaying operation
client-stubs: 6.01:setValue1Completed
client-stubs       SUCCESS
client-stubs: 6.02:timerCallback launching setvalue1
client-stubs: 6.02:timerCallback setvalue1 launched
client-stubs: 7.02:timerCallback launching setvalue1
client-stubs: 7.02:timerCallback setvalue1 launched
...
client-stubs:25.04:timerCallback launching setvalue1
client-stubs:25.04:timerCallback setvalue1 launched
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=-30)
server:value_object_setvalue1: Delaying operation
client-stubs:26.03:setValue1Completed
client-stubs       SUCCESS
client-stubs:26.05:timerCallback launching setvalue1
client-stubs:26.05:timerCallback setvalue1 launched
client-stubs:27.05:timerCallback launching setvalue1
client-stubs:27.05:timerCallback setvalue1 launched
client-stubs:28.05:timerCallback launching setvalue1
client-stubs:28.05:timerCallback setvalue1 launched
client-stubs:29.05:timerCallback launching setvalue1
client-stubs:29.05:timerCallback setvalue1 launched
client-stubs:30.05:timerCallback launching setvalue1
client-stubs:30.05:timerCallback setvalue1 launched
client-stubs:31.02:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=-20)
server:value_object_setvalue1: Delaying operation
client-stubs:31.05:timerCallback launching setvalue1
client-stubs:31.05:timerCallback setvalue1 launched
client-stubs:32.03:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
client-stubs:32.05:timerCallback launching setvalue1
client-stubs:32.05:timerCallback setvalue1 launched
client-stubs:33.03:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
client-stubs:33.05:timerCallback launching setvalue1
client-stubs:33.05:timerCallback setvalue1 launched
client-stubs:34.03:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
client-stubs:34.06:timerCallback launching setvalue1
client-stubs:34.06:timerCallback setvalue1 launched
client-stubs:35.03:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
client-stubs:35.05:timerCallback launching setvalue1
client-stubs:35.05:timerCallback setvalue1 launched
client-stubs:36.04:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=-10)
server:value_object_setvalue1: Delaying operation
client-stubs:36.06:timerCallback launching setvalue1
client-stubs:36.06:timerCallback setvalue1 launched
client-stubs:37.04:setValue1Completed
client-stubs       ERROR: Did not receive a reply. Possible causes include:
 the remote application did not send a reply, the message bus security policy
 blocked the reply, the reply timeout expired, or the network connection was
 broken.
[Ctrl+c]
[sbox-FREMANTLE_X86: /glib-dbus-async] >
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=30)
server:value_object_setvalue1: Delaying operation
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=40)
server:value_object_setvalue1: Delaying operation
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
server:value_object_setvalue1: Called (valueIn=50)
server:value_object_setvalue1: Delaying operation
server:value_object_emitSignal: Emitting signal id 0, with message 'value1'
...

What happens above is rather subtle. The timer callback in the client launches once per second and performs the RPC method launch. The server, however, still has the 5 second delay for each method call in it. The successive launches go on without any responses for a while. The first response comes back at about 6 seconds from the starting of the client. At this point, the server already has four other outstanding method calls that it has not handled. Slowly the method calls are accumulating at the server end and it does not deal with them quickly enough to satisfy the client.

After about 30 seconds, the setValue1Completed callback is invoked, but the method call fails. This has managed to trigger the method call timeout mechanism. After this point, all the method calls that have accumulated into the server (into a message queue) fail in the client, since they all return late now, even if the server actually does handle them.

Once the client is terminated, the server is still happily continuing serving the requests, oblivious to the fact that there is no client to process the responses.

The above test demonstrates quite brutally that the services need to be designed properly, so that there is a clearly defined protocol what to do in case a method call is delayed. It is also advisable to design a notification protocol to tell clients that something has completed, instead of forcing them to time out. Using D-Bus signals is one way. However, signals must not be generated when nothing is listening to them. This can be done by only sending signals when a long operation finishes (assuming this has been documented as part of the service description).

One partial fix is for the client to track and make sure that only one method call to one service is outstanding at any given time. So, instead of just blindly launching the RPC methods, the client should defer from launching RPC methods if it has not yet received a response from the server (and the call has not timed out).

However, this fix is not complete, because the same problem manifests itself once there are multiple clients running in parallel and requesting the same methods. The proper fix is to make the server capable of serving multiple requests in parallel.

Asynchronous Method Calls Using GLib Wrappers

Sometimes the interface XML is missing, so the dbus-bindings-tool cannot be run to generate the stub code. The GLib wrappers are generic enough to enable building own method calls, when necessary.

Starting with some known generated stub code to see which parts can possibly be reused with some modifications is usually the easiest solution. This is shown in the last step of this example, in order to make a version of the asynchronous client that works without the stub generator.

The first step is to take a peek at the stub-generated code for the setvalue1 call (when used asynchronously): glib-dbus-async/value-client-stub.h

typedef void (*org_maemo_Value_setvalue1_reply) (DBusGProxy *proxy,
                                                 GError *error,
                                                 gpointer userdata);
static void
org_maemo_Value_setvalue1_async_callback (DBusGProxy *proxy,
                                          DBusGProxyCall *call,
                                          void *user_data)
{
  DBusGAsyncData *data = user_data;
  GError *error = NULL;
  dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID);
  (*(org_maemo_Value_setvalue1_reply)data->cb) (proxy, error,
                                                data->userdata);
  return;
}
static
#ifdef G_HAVE_INLINE
inline
#endif
DBusGProxyCall*
org_maemo_Value_setvalue1_async (DBusGProxy *proxy,
                                 const gint IN_new_value,
                             org_maemo_Value_setvalue1_reply callback,
                                 gpointer userdata)
{
  DBusGAsyncData *stuff;
  stuff = g_new (DBusGAsyncData, 1);
  stuff->cb = G_CALLBACK (callback);
  stuff->userdata = userdata;
  return dbus_g_proxy_begin_call (
    proxy, "setvalue1", org_maemo_Value_setvalue1_async_callback,
    stuff, g_free, G_TYPE_INT, IN_new_value, G_TYPE_INVALID);
}

What is notable in the code snippet above is that the _async method creates a temporary small structure that holds the pointer to the callback function and a copy of the userdata pointer. This small structure is then passed to dbus_g_proxy_begin_call, along with the address of the generated callback wrapper function (org_maemo_Value_setvalue1_async_callback). The GLib async launcher also takes a function pointer to a function to be used when the supplied "user-data" (in this case, the small structure) needs to be disposed of after the call. Because it uses g_new to allocate the small structure, it passes g_free as the freeing function. Next comes the argument specification for the method call, which obeys the same rules as the LibOSSO ones.

On RPC completion, the generated callback is invoked and it gets the real callback function pointer and the userdata as its "user-data" parameter. Firs it collects the exit code for the call with dbus_g_proxy_end_call, unpacks the data and invokes the real callback. After returning, the GLib wrappers (which called the generated callback) calls g_free to release the small structure, and the whole RPC launch ends.

The next step is to re-implement pretty much the same logic, but also dispose of the small structure because the callback is implemented directly, not as a wrapper-callback (it also omits the need for one memory allocation and one free).

The first step for that is to implement the RPC asynchronous launch code: glib-dbus-async/client-glib.c

/**
 * This function will be called repeatedly from within the mainloop
 * timer launch code.
 *
 * It will launch asynchronous RPC method to set value1 with ever
 * increasing argument.
 */
static gboolean timerCallback(DBusGProxy* remoteobj) {
 
  /* Local value that we'll start updating to the remote object. */
  static gint localValue1 = -80;
 
  /* Start the first RPC.
     The call using GLib/D-Bus is only slightly more complex than the
     stubs. The overall operation is the same. */
  g_print(PROGNAME ":timerCallback launching setvalue1\n");
  dbus_g_proxy_begin_call(remoteobj,
                          /* Method name. */
                          "setvalue1",
                          /* Callback to call on "completion". */
                          setValue1Completed,
                          /* User-data to pass to callback. */
                          NULL,
                          /* Function to call to free userData after
                             callback returns. */
                          NULL,
                          /* First argument GType. */
                          G_TYPE_INT,
                          /* First argument value (passed by value) */
                          localValue1,
                          /* Terminate argument list. */
                          G_TYPE_INVALID);
  g_print(PROGNAME ":timerCallback setvalue1 launched\n");
 
  /* Step the local value forward. */
  localValue1 += 10;
 
  /* Repeat timer later. */
  return TRUE;
}

And the callback that is invoked on method call completion timeouts or errors: glib-dbus-async/client-glib.c

/**
 * This function is called when the async setvalue1 either
 * completes, timeouts or fails (same as before). The main difference in
 * using GLib/D-Bus wrappers is that we need to "collect" the return
 * value (or error). This is done with the _end_call function.
 *
 * Note that all callbacks that are to be registered for RPC async
 * notifications using dbus_g_proxy_begin_call must follow the
 * following prototype: DBusGProxyCallNotify .
 */
static void setValue1Completed(DBusGProxy* proxy,
                               DBusGProxyCall* call,
                               gpointer userData) {
 
  /* This will hold the GError object (if any). */
  GError* error = NULL;
 
  g_print(PROGNAME ":setValue1Completed\n");
 
  /* We next need to collect the results from the RPC call.
     The function returns FALSE on errors (which we check), although
     we could also check whether error-ptr is still NULL. */
  if (!dbus_g_proxy_end_call(proxy,
                             /* The call that we're collecting. */
                             call,
                             /* Where to store the error (if any). */
                             &error,
                             /* Next we list the GType codes for all
                                the arguments we expect back. In our
                                case there are none, so set to
                                invalid. */
                             G_TYPE_INVALID)) {
    /* Some error occurred while collecting the result. */
    g_printerr(PROGNAME " ERROR: %s\n", error->message);
    g_error_free(error);
  } else {
    g_print(PROGNAME " SUCCESS\n");
  }
}

The generated stub code is no longer needed, so the dependency rules for the stubless GLib version are different: glib-dbus-async/Makefile

client-glib: client-glib.o
        $(CC) $^ -o $@ $(LDFLAGS)
# Note that the GLib client doesn't need the stub code.
client-glib.o: client-glib.c common-defs.h
        $(CC) $(CFLAGS) -DPROGNAME=\"$(basename $@)\" -c $< -o $@

Since the example program logic has not changed from the previous version, testing client-glib is not presented here (it can of course be tested if so desired, because the source code contains the fully working program). This version of the client also launches the method calls without waiting for previous method calls to complete.