proper shutdown
[oweals/gnunet.git] / src / util / client.c
index 9a2f4767887c99d7f2e7e8e251c0b1ecd26af7df..7e688059044211cebf40dcea045f51534252b4a0 100644 (file)
 
 #define DEBUG_CLIENT GNUNET_NO
 
+/**
+ * How often do we re-try tranmsitting requests before giving up?
+ * Note that if we succeeded transmitting a request but failed to read
+ * a response, we do NOT re-try.
+ */
+#define MAX_ATTEMPTS 50
+
+/**
+ * Handle for a transmission request.
+ */
+struct GNUNET_CLIENT_TransmitHandle
+{
+  /**
+   * Connection state.
+   */
+  struct GNUNET_CLIENT_Connection *sock;
+
+  /**
+   * Function to call to get the data for transmission.
+   */
+  GNUNET_CONNECTION_TransmitReadyNotify notify;
+
+  /**
+   * Closure for notify.
+   */
+  void *notify_cls;
+
+  /**
+   * Handle to the transmission with the underlying
+   * connection.
+   */
+  struct GNUNET_CONNECTION_TransmitHandle *th;
+
+  /**
+   * If we are re-trying and are delaying to do so,
+   * handle to the scheduled task managing the delay.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
+
+  /**
+   * Timeout for the operation overall.
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Number of bytes requested.
+   */
+  size_t size;
+
+  /**
+   * Are we allowed to re-try to connect without telling
+   * the user (of this API) about the connection troubles?
+   */
+  int auto_retry;
+
+  /**
+   * Number of attempts left for transmitting the request.  We may
+   * fail the first time (say because the service is not yet up), in
+   * which case (if auto_retry is set) we wait a bit and re-try
+   * (timeout permitting).
+   */
+  unsigned int attempts_left;
+
+};
+
+
+/**
+ * Context for processing 
+ * "GNUNET_CLIENT_transmit_and_get_response" requests.
+ */
+struct TransmitGetResponseContext
+{
+  /**
+   * Client handle.
+   */
+  struct GNUNET_CLIENT_Connection *sock;
+
+  /**
+   * Message to transmit; do not free, allocated
+   * right after this struct.
+   */
+  const struct GNUNET_MessageHeader *hdr;
+
+  /**
+   * Timeout to use.
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Function to call when done.
+   */
+  GNUNET_CLIENT_MessageHandler rn;
+
+  /**
+   * Closure for "rn".
+   */
+  void *rn_cls;
+};
+
 /**
  * Struct to refer to a GNUnet TCP connection.
  * This is more than just a socket because if the server
@@ -51,9 +150,10 @@ struct GNUNET_CLIENT_Connection
   struct GNUNET_CONNECTION_Handle *sock;
 
   /**
-   * Our scheduler.
+   * Our configuration.
+   * FIXME: why do we DUP the configuration? Avoid this!
    */
-  struct GNUNET_SCHEDULER_Handle *sched;
+  struct GNUNET_CONFIGURATION_Handle *cfg;
 
   /**
    * Name of the service we interact with.
@@ -61,9 +161,10 @@ struct GNUNET_CLIENT_Connection
   char *service_name;
 
   /**
-   * ID of task used for receiving.
+   * Context of a transmit_and_get_response operation, NULL
+   * if no such operation is pending.
    */
-  GNUNET_SCHEDULER_TaskIdentifier receiver_task;
+  struct TransmitGetResponseContext *tag;
 
   /**
    * Handler for current receiver task.
@@ -75,11 +176,28 @@ struct GNUNET_CLIENT_Connection
    */
   void *receiver_handler_cls;
 
+  /**
+   * Handle for a pending transmission request, NULL if there is
+   * none pending.
+   */
+  struct GNUNET_CLIENT_TransmitHandle *th;
+
   /**
    * Handler for service test completion (NULL unless in service_test)
    */
   GNUNET_SCHEDULER_Task test_cb;
 
+  /**
+   * Deadline for calling 'test_cb'.
+   */
+  struct GNUNET_TIME_Absolute test_deadline;
+
+  /**
+   * If we are re-trying and are delaying to do so,
+   * handle to the scheduled task managing the delay.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier receive_task;
+
   /**
    * Closure for test_cb (NULL unless in service_test)
    */
@@ -95,6 +213,12 @@ struct GNUNET_CLIENT_Connection
    */
   struct GNUNET_TIME_Absolute receive_timeout;
 
+  /**
+   * Current value for our incremental back-off (for
+   * connect re-tries).
+   */
+  struct GNUNET_TIME_Relative back_off;
+
   /**
    * Number of bytes in received_buf that are valid.
    */
@@ -110,27 +234,68 @@ struct GNUNET_CLIENT_Connection
    */
   int msg_complete;
 
+  /**
+   * Are we currently busy doing receive-processing?
+   * GNUNET_YES if so, GNUNET_NO if not.
+   */
+  int in_receive;
+
+  /**
+   * Are we ignoring shutdown signals?
+   */
+  int ignore_shutdown;
+  
+  /**
+   * How often have we tried to connect?
+   */
+  unsigned int attempts;
+
 };
 
 
 /**
- * Get a connection with a service.
+ * Try to connect to the service.
  *
- * @param sched scheduler to use
- * @param service_name name of the service
+ * @param service_name name of service to connect to
  * @param cfg configuration to use
- * @return NULL on error (service unknown to configuration)
+ * @param attempt counter used to alternate between IP and UNIX domain sockets
+ * @return NULL on error
  */
-struct GNUNET_CLIENT_Connection *
-GNUNET_CLIENT_connect (struct GNUNET_SCHEDULER_Handle *sched,
-                       const char *service_name,
-                       const struct GNUNET_CONFIGURATION_Handle *cfg)
+static struct GNUNET_CONNECTION_Handle *
+do_connect (const char *service_name,
+            const struct GNUNET_CONFIGURATION_Handle *cfg,
+           unsigned int attempt)
 {
-  struct GNUNET_CLIENT_Connection *ret;
   struct GNUNET_CONNECTION_Handle *sock;
   char *hostname;
+  char *unixpath;
   unsigned long long port;
 
+  sock = NULL;
+#if AF_UNIX
+  if (0 == (attempt % 2))
+    {
+      /* on even rounds, try UNIX */
+      if ((GNUNET_OK ==
+         GNUNET_CONFIGURATION_get_value_string (cfg,
+                                                service_name,
+                                                "UNIXPATH", &unixpath)) &&
+          (0 < strlen (unixpath))) /* We have a non-NULL unixpath, does that mean it's valid? */
+       {
+          sock = GNUNET_CONNECTION_create_from_connect_to_unixpath (cfg, unixpath);
+         if (sock != NULL)
+           {
+#if DEBUG_CLIENT
+              GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Connected to unixpath `%s'!\n", unixpath);
+#endif
+              GNUNET_free(unixpath);
+              return sock;
+           }
+       }
+      GNUNET_free_non_null (unixpath);
+    }
+#endif
+
   if ((GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_number (cfg,
                                               service_name,
@@ -143,67 +308,157 @@ GNUNET_CLIENT_connect (struct GNUNET_SCHEDULER_Handle *sched,
                                               "HOSTNAME", &hostname)))
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Could not determine valid hostname and port for service `%s' from configuration.\n",
+                  _("Could not determine valid hostname and port for service `%s' from configuration.\n"),
                   service_name);
       return NULL;
     }
-  sock = GNUNET_CONNECTION_create_from_connect (sched,
-                                                    hostname,
-                                                    port,
-                                                    GNUNET_SERVER_MAX_MESSAGE_SIZE);
+  if (0 == strlen (hostname))
+    {
+      GNUNET_free (hostname);
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Need a non-empty hostname for service `%s'.\n"),
+                  service_name);
+      return NULL;
+    }
+  if (port == 0)
+    {
+#if AF_UNIX
+      if (0 != (attempt % 2))
+       {
+         /* try UNIX */
+         if ((GNUNET_OK ==
+             GNUNET_CONFIGURATION_get_value_string (cfg,
+                                                    service_name,
+                                                    "UNIXPATH", &unixpath)) &&
+              (0 < strlen (unixpath)))
+           {
+             sock = GNUNET_CONNECTION_create_from_connect_to_unixpath (cfg,
+                                                                       unixpath);
+             GNUNET_free (unixpath);
+             if (sock != NULL)
+               return sock;            
+           }
+       }
+#endif
+#if DEBUG_CLIENT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
+                 "Port is 0 for service `%s', UNIXPATH did not work, returning NULL!\n",
+                 service_name);
+#endif
+      GNUNET_free (hostname);
+      return NULL;
+    }
+
+  sock = GNUNET_CONNECTION_create_from_connect (cfg,
+                                                hostname,
+                                                port);
   GNUNET_free (hostname);
+  return sock;
+}
+
+
+/**
+ * Get a connection with a service.
+ *
+ * @param service_name name of the service
+ * @param cfg configuration to use
+ * @return NULL on error (service unknown to configuration)
+ */
+struct GNUNET_CLIENT_Connection *
+GNUNET_CLIENT_connect (const char *service_name,
+                       const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  struct GNUNET_CLIENT_Connection *ret;
+  struct GNUNET_CONNECTION_Handle *sock;
+
+  sock = do_connect (service_name,
+                    cfg, 0);
   if (sock == NULL)
     return NULL;
   ret = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_Connection));
+  ret->attempts = 1;
   ret->sock = sock;
-  ret->sched = sched;
   ret->service_name = GNUNET_strdup (service_name);
+  ret->cfg = GNUNET_CONFIGURATION_dup (cfg);
+  ret->back_off = GNUNET_TIME_UNIT_MILLISECONDS;
   return ret;
 }
 
 
 /**
- * Receiver task has completed, free rest of client
- * data structures.
+ * Configure this connection to ignore shutdown signals.
+ *
+ * @param h client handle
+ * @param do_ignore GNUNET_YES to ignore, GNUNET_NO to restore default
  */
-static void
-finish_cleanup (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+void
+GNUNET_CLIENT_ignore_shutdown (struct GNUNET_CLIENT_Connection *h,
+                              int do_ignore)
 {
-  struct GNUNET_CLIENT_Connection *sock = cls;
-
-  GNUNET_array_grow (sock->received_buf, sock->received_size, 0);
-  GNUNET_free (sock->service_name);
-  GNUNET_free (sock);
+  h->ignore_shutdown = do_ignore;
+  if (h->sock != NULL)
+    GNUNET_CONNECTION_ignore_shutdown (h->sock,
+                                      do_ignore);
 }
 
 
 /**
- * Destroy connection with the service.
+ * Destroy connection with the service.  This will automatically
+ * cancel any pending "receive" request (however, the handler will
+ * *NOT* be called, not even with a NULL message).  Any pending
+ * transmission request will also be cancelled UNLESS the callback for
+ * the transmission request has already been called, in which case the
+ * transmission 'finish_pending_write' argument determines whether or
+ * not the write is guaranteed to complete before the socket is fully
+ * destroyed (unless, of course, there is an error with the server in
+ * which case the message may still be lost).
+ *
+ * @param finish_pending_write should a transmission already passed to the
+ *          handle be completed?
+ * @param sock handle to the service connection
  */
 void
-GNUNET_CLIENT_disconnect (struct GNUNET_CLIENT_Connection *sock)
+GNUNET_CLIENT_disconnect (struct GNUNET_CLIENT_Connection *sock,
+                         int finish_pending_write)
 {
   GNUNET_assert (sock->sock != NULL);
-  GNUNET_CONNECTION_destroy (sock->sock);
+  if (sock->in_receive == GNUNET_YES)
+    {
+      GNUNET_CONNECTION_receive_cancel (sock->sock);
+      sock->in_receive = GNUNET_NO;
+    }
+  GNUNET_CONNECTION_destroy (sock->sock, finish_pending_write);
   sock->sock = NULL;
+  if (sock->tag != NULL)
+    {
+      GNUNET_free (sock->tag);
+      sock->tag = NULL;
+    }
   sock->receiver_handler = NULL;
-  GNUNET_SCHEDULER_add_after (sock->sched,
-                              GNUNET_YES,
-                              GNUNET_SCHEDULER_PRIORITY_KEEP,
-                              sock->receiver_task, &finish_cleanup, sock);
+  if (sock->th != NULL)
+    GNUNET_CLIENT_notify_transmit_ready_cancel (sock->th);
+  if (sock->receive_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_SCHEDULER_cancel (sock->receive_task);
+      sock->receive_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  GNUNET_array_grow (sock->received_buf, sock->received_size, 0);
+  GNUNET_free (sock->service_name);
+  GNUNET_CONFIGURATION_destroy (sock->cfg);
+  GNUNET_free (sock);
 }
 
 
 /**
- * check if message is complete
+ * Check if message is complete
  */
 static void
 check_complete (struct GNUNET_CLIENT_Connection *conn)
 {
   if ((conn->received_pos >= sizeof (struct GNUNET_MessageHeader)) &&
       (conn->received_pos >=
-       ntohs (((const struct GNUNET_MessageHeader *) conn->
-               received_buf)->size)))
+       ntohs (((const struct GNUNET_MessageHeader *) conn->received_buf)->
+              size)))
     conn->msg_complete = GNUNET_YES;
 }
 
@@ -228,17 +483,22 @@ receive_helper (void *cls,
 {
   struct GNUNET_CLIENT_Connection *conn = cls;
   struct GNUNET_TIME_Relative remaining;
+  GNUNET_CLIENT_MessageHandler receive_handler;
+  void *receive_handler_cls;
 
   GNUNET_assert (conn->msg_complete == GNUNET_NO);
-  conn->receiver_task = GNUNET_SCHEDULER_NO_TASK;
-
+  conn->in_receive = GNUNET_NO;
   if ((available == 0) || (conn->sock == NULL) || (errCode != 0))
     {
       /* signal timeout! */
-      if (conn->receiver_handler != NULL)
+#if DEBUG_CLIENT
+      GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "timeout in receive_helper, available %d, conn->sock %s, errCode %d\n", available, conn->sock == NULL ? "NULL" : "non-NULL", errCode);
+#endif
+      if (NULL != (receive_handler = conn->receiver_handler))
         {
-          conn->receiver_handler (conn->receiver_handler_cls, NULL);
+          receive_handler_cls = conn->receiver_handler_cls;
           conn->receiver_handler = NULL;
+          receive_handler (receive_handler_cls, NULL);
         }
       return;
     }
@@ -256,7 +516,7 @@ receive_helper (void *cls,
   check_complete (conn);
   /* check for timeout */
   remaining = GNUNET_TIME_absolute_get_remaining (conn->receive_timeout);
-  if (remaining.value == 0)
+  if (remaining.rel_value == 0)
     {
       /* signal timeout! */
       conn->receiver_handler (conn->receiver_handler_cls, NULL);
@@ -271,30 +531,40 @@ receive_helper (void *cls,
 
 /**
  * Continuation to call the receive callback.
+ *
+ * @param cls  our handle to the client connection
+ * @param tc scheduler context
  */
 static void
-receive_task (void *scls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+receive_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
 {
-  struct GNUNET_CLIENT_Connection *sock = scls;
+  struct GNUNET_CLIENT_Connection *sock = cls;
   GNUNET_CLIENT_MessageHandler handler = sock->receiver_handler;
-  const struct GNUNET_MessageHeader *cmsg = (const struct GNUNET_MessageHeader *) sock->received_buf;
-  void *cls = sock->receiver_handler_cls;
+  const struct GNUNET_MessageHeader *cmsg =
+    (const struct GNUNET_MessageHeader *) sock->received_buf;
+  void *handler_cls = sock->receiver_handler_cls;
   uint16_t msize = ntohs (cmsg->size);
   char mbuf[msize];
-  struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader*) mbuf;
+  struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) mbuf;
 
+#if DEBUG_CLIENT
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Received message of type %u and size %u\n",
+             ntohs (cmsg->type),
+             msize);
+#endif
+  sock->receive_task = GNUNET_SCHEDULER_NO_TASK;
   GNUNET_assert (GNUNET_YES == sock->msg_complete);
-  sock->receiver_task = GNUNET_SCHEDULER_NO_TASK;
   GNUNET_assert (sock->received_pos >= msize);
   memcpy (msg, cmsg, msize);
   memmove (sock->received_buf,
-          &sock->received_buf[msize], sock->received_pos - msize);
+           &sock->received_buf[msize], sock->received_pos - msize);
   sock->received_pos -= msize;
   sock->msg_complete = GNUNET_NO;
-  sock->receiver_handler = NULL;  
+  sock->receiver_handler = NULL;
   check_complete (sock);
   if (handler != NULL)
-    handler (cls, msg);
+    handler (handler_cls, msg);
 }
 
 
@@ -318,52 +588,25 @@ GNUNET_CLIENT_receive (struct GNUNET_CLIENT_Connection *sock,
       handler (handler_cls, NULL);
       return;
     }
-  GNUNET_assert (sock->receiver_task ==
-                 GNUNET_SCHEDULER_NO_TASK);
   sock->receiver_handler = handler;
   sock->receiver_handler_cls = handler_cls;
   sock->receive_timeout = GNUNET_TIME_relative_to_absolute (timeout);
   if (GNUNET_YES == sock->msg_complete)
-    sock->receiver_task = GNUNET_SCHEDULER_add_after (sock->sched,
-                                                      GNUNET_YES,
-                                                      GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                      GNUNET_SCHEDULER_NO_TASK,
-                                                      &receive_task, sock);
+    {
+      sock->receive_task = GNUNET_SCHEDULER_add_after (GNUNET_SCHEDULER_NO_TASK,
+                                                       &receive_task, sock);
+    }
   else
-    sock->receiver_task = GNUNET_CONNECTION_receive (sock->sock,
-                                                    GNUNET_SERVER_MAX_MESSAGE_SIZE,
-                                                    timeout,
-                                                    &receive_helper, sock);
-}
-
-
-static size_t
-write_shutdown (void *cls, size_t size, void *buf)
-{
-  struct GNUNET_MessageHeader *msg;
-
-  if (size < sizeof (struct GNUNET_MessageHeader))
-    return 0;                   /* client disconnected */
-  msg = (struct GNUNET_MessageHeader *) buf;
-  msg->type = htons (GNUNET_MESSAGE_TYPE_SHUTDOWN);
-  msg->size = htons (sizeof (struct GNUNET_MessageHeader));
-  return sizeof (struct GNUNET_MessageHeader);
-}
-
-
-/**
- * Request that the service should shutdown.
- * Afterwards, the connection should be disconnected.
- *
- * @param sock the socket connected to the service
- */
-void
-GNUNET_CLIENT_service_shutdown (struct GNUNET_CLIENT_Connection *sock)
-{
-  GNUNET_CONNECTION_notify_transmit_ready (sock->sock,
-                                        sizeof (struct GNUNET_MessageHeader),
-                                        GNUNET_TIME_UNIT_FOREVER_REL,
-                                        &write_shutdown, NULL);
+    {
+      GNUNET_assert (sock->in_receive == GNUNET_NO);
+      sock->in_receive = GNUNET_YES;
+#if DEBUG_CLIENT
+      GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "calling GNUNET_CONNECTION_receive\n");
+#endif
+      GNUNET_CONNECTION_receive (sock->sock,
+                                 GNUNET_SERVER_MAX_MESSAGE_SIZE - 1,
+                                 timeout, &receive_helper, sock);
+    }
 }
 
 
@@ -371,12 +614,9 @@ GNUNET_CLIENT_service_shutdown (struct GNUNET_CLIENT_Connection *sock)
  * Report service unavailable.
  */
 static void
-service_test_error (struct GNUNET_SCHEDULER_Handle *s,
-                    GNUNET_SCHEDULER_Task task, void *task_cls)
+service_test_error (GNUNET_SCHEDULER_Task task, void *task_cls)
 {
-  GNUNET_SCHEDULER_add_continuation (s,
-                                     GNUNET_YES,
-                                     task,
+  GNUNET_SCHEDULER_add_continuation (task,
                                      task_cls,
                                      GNUNET_SCHEDULER_REASON_TIMEOUT);
 }
@@ -401,23 +641,22 @@ confirm_handler (void *cls, const struct GNUNET_MessageHeader *msg)
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Received confirmation that service is running.\n");
 #endif
-      GNUNET_SCHEDULER_add_continuation (conn->sched,
-                                         GNUNET_YES,
-                                         conn->test_cb,
+      GNUNET_SCHEDULER_add_continuation (conn->test_cb,
                                          conn->test_cb_cls,
                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
     }
   else
     {
-      service_test_error (conn->sched, conn->test_cb, conn->test_cb_cls);
+      service_test_error (conn->test_cb, conn->test_cb_cls);
     }
-  GNUNET_CLIENT_disconnect (conn);
+  GNUNET_CLIENT_disconnect (conn, GNUNET_NO);
 }
 
 
 static size_t
 write_test (void *cls, size_t size, void *buf)
 {
+  struct GNUNET_CLIENT_Connection *conn = cls;
   struct GNUNET_MessageHeader *msg;
 
   if (size < sizeof (struct GNUNET_MessageHeader))
@@ -426,14 +665,21 @@ write_test (void *cls, size_t size, void *buf)
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   _("Failure to transmit TEST request.\n"));
 #endif
+      service_test_error (conn->test_cb, conn->test_cb_cls);
+      GNUNET_CLIENT_disconnect (conn, GNUNET_NO);
       return 0;                 /* client disconnected */
     }
 #if DEBUG_CLIENT
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Transmitting TEST request.\n"));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Transmitting `%s' request.\n", "TEST");
 #endif
   msg = (struct GNUNET_MessageHeader *) buf;
   msg->type = htons (GNUNET_MESSAGE_TYPE_TEST);
   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
+  GNUNET_CLIENT_receive (conn, 
+                        &confirm_handler, 
+                        conn, 
+                        GNUNET_TIME_absolute_get_remaining (conn->test_deadline));
   return sizeof (struct GNUNET_MessageHeader);
 }
 
@@ -441,7 +687,6 @@ write_test (void *cls, size_t size, void *buf)
 /**
  * Wait until the service is running.
  *
- * @param sched scheduler to use
  * @param service name of the service to wait for
  * @param cfg configuration to use
  * @param timeout how long to wait at most in ms
@@ -451,8 +696,7 @@ write_test (void *cls, size_t size, void *buf)
  * @param task_cls closure for task
  */
 void
-GNUNET_CLIENT_service_test (struct GNUNET_SCHEDULER_Handle *sched,
-                            const char *service,
+GNUNET_CLIENT_service_test (const char *service,
                             const struct GNUNET_CONFIGURATION_Handle *cfg,
                             struct GNUNET_TIME_Relative timeout,
                             GNUNET_SCHEDULER_Task task, void *task_cls)
@@ -463,32 +707,153 @@ GNUNET_CLIENT_service_test (struct GNUNET_SCHEDULER_Handle *sched,
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Testing if service `%s' is running.\n", service);
 #endif
-  conn = GNUNET_CLIENT_connect (sched, service, cfg);
+  conn = GNUNET_CLIENT_connect (service, cfg);
   if (conn == NULL)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   _
                   ("Could not connect to service `%s', must not be running.\n"),
                   service);
-      service_test_error (sched, task, task_cls);
+      service_test_error (task, task_cls);
       return;
     }
   conn->test_cb = task;
   conn->test_cb_cls = task_cls;
-  if (NULL ==
-      GNUNET_CONNECTION_notify_transmit_ready (conn->sock,
-                                            sizeof (struct
-                                                    GNUNET_MessageHeader),
-                                            timeout, &write_test, NULL))
+  conn->test_deadline = GNUNET_TIME_relative_to_absolute (timeout);
+
+  if (NULL == GNUNET_CLIENT_notify_transmit_ready (conn,
+                                                  sizeof (struct GNUNET_MessageHeader),
+                                                  timeout,
+                                                  GNUNET_YES,
+                                                  &write_test, conn))  
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                   _("Failure to transmit request to service `%s'\n"),
                   service);
-      service_test_error (sched, task, task_cls);
-      GNUNET_CLIENT_disconnect (conn);
+      service_test_error (task, task_cls);
+      GNUNET_CLIENT_disconnect (conn, GNUNET_NO);
+      return;
+    }
+}
+
+
+/**
+ * Connection notifies us about failure or success of
+ * a transmission request.  Either pass it on to our
+ * user or, if possible, retry.
+ *
+ * @param cls our "struct GNUNET_CLIENT_TransmissionHandle"
+ * @param size number of bytes available for transmission
+ * @param buf where to write them
+ * @return number of bytes written to buf
+ */
+static size_t client_notify (void *cls, size_t size, void *buf);
+
+
+/**
+ * This task is run if we should re-try connection to the
+ * service after a while.
+ *
+ * @param cls our "struct GNUNET_CLIENT_TransmitHandle" of the request
+ * @param tc unused
+ */
+static void
+client_delayed_retry (void *cls,
+                      const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct GNUNET_CLIENT_TransmitHandle *th = cls;
+
+  th->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
+  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
+    {
+#if DEBUG_CLIENT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Transmission failed due to shutdown.\n");
+#endif
+      th->sock->th = NULL;
+      th->notify (th->notify_cls, 0, NULL);
+      GNUNET_free (th);
+      return;
+    }
+  th->th = GNUNET_CONNECTION_notify_transmit_ready (th->sock->sock,
+                                                    th->size,
+                                                    GNUNET_TIME_absolute_get_remaining
+                                                    (th->timeout),
+                                                    &client_notify, th);
+  if (th->th == NULL)
+    {
+      GNUNET_break (0);
+      th->notify (th->notify_cls, 0, NULL);
+      GNUNET_free (th);
       return;
     }
-  GNUNET_CLIENT_receive (conn, &confirm_handler, conn, timeout);
+}
+
+
+/**
+ * Connection notifies us about failure or success of a transmission
+ * request.  Either pass it on to our user or, if possible, retry.
+ *
+ * @param cls our "struct GNUNET_CLIENT_TransmissionHandle"
+ * @param size number of bytes available for transmission
+ * @param buf where to write them
+ * @return number of bytes written to buf
+ */
+static size_t
+client_notify (void *cls, size_t size, void *buf)
+{
+  struct GNUNET_CLIENT_TransmitHandle *th = cls;
+  size_t ret;
+  struct GNUNET_TIME_Relative delay;
+
+  th->th = NULL;
+  th->sock->th = NULL;
+  if (buf == NULL)
+    {
+      delay = GNUNET_TIME_absolute_get_remaining (th->timeout);
+      delay.rel_value /= 2;
+      if ( (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & GNUNET_SCHEDULER_get_reason ())) ||
+          (GNUNET_YES != th->auto_retry) ||
+          (0 == --th->attempts_left) || 
+          (delay.rel_value < 1) )
+        {
+#if DEBUG_CLIENT
+          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                      "Transmission failed %u times, giving up.\n",
+                      MAX_ATTEMPTS - th->attempts_left);
+#endif
+          GNUNET_break (0 == th->notify (th->notify_cls, 0, NULL));
+          GNUNET_free (th);
+          return 0;
+        }
+      /* auto-retry */
+      GNUNET_CONNECTION_destroy (th->sock->sock, GNUNET_NO);
+      th->sock->sock = do_connect (th->sock->service_name,
+                                  th->sock->cfg,
+                                  th->sock->attempts++);
+      GNUNET_assert (NULL != th->sock->sock);
+      GNUNET_CONNECTION_ignore_shutdown (th->sock->sock,
+                                        th->sock->ignore_shutdown);
+      delay = GNUNET_TIME_relative_min (delay, th->sock->back_off);
+      th->sock->back_off 
+         = GNUNET_TIME_relative_min (GNUNET_TIME_relative_multiply (th->sock->back_off, 2),
+                                   GNUNET_TIME_UNIT_SECONDS);
+#if DEBUG_CLIENT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Transmission failed %u times, trying again in %llums.\n",
+                  MAX_ATTEMPTS - th->attempts_left,
+                  (unsigned long long) delay.rel_value);
+#endif
+      th->reconnect_task = GNUNET_SCHEDULER_add_delayed (delay,
+                                                         &client_delayed_retry,
+                                                         th);
+      th->sock->th = th;
+      return 0;
+    }
+  GNUNET_assert (size >= th->size);
+  ret = th->notify (th->notify_cls, size, buf);
+  GNUNET_free (th);
+  return ret;
 }
 
 
@@ -501,56 +866,74 @@ GNUNET_CLIENT_service_test (struct GNUNET_SCHEDULER_Handle *sched,
  * @param size number of bytes to send
  * @param timeout after how long should we give up (and call
  *        notify with buf NULL and size 0)?
+ * @param auto_retry if the connection to the service dies, should we
+ *        automatically re-connect and retry (within the timeout period)
+ *        or should we immediately fail in this case?  Pass GNUNET_YES
+ *        if the caller does not care about temporary connection errors,
+ *        for example because the protocol is stateless
  * @param notify function to call
  * @param notify_cls closure for notify
  * @return NULL if our buffer will never hold size bytes,
  *         a handle if the notify callback was queued (can be used to cancel)
  */
-struct GNUNET_CONNECTION_TransmitHandle *
+struct GNUNET_CLIENT_TransmitHandle *
 GNUNET_CLIENT_notify_transmit_ready (struct GNUNET_CLIENT_Connection *sock,
                                      size_t size,
                                      struct GNUNET_TIME_Relative timeout,
+                                     int auto_retry,
                                      GNUNET_CONNECTION_TransmitReadyNotify
                                      notify, void *notify_cls)
 {
-  return GNUNET_CONNECTION_notify_transmit_ready (sock->sock,
-                                               size,
-                                               timeout, notify, notify_cls);
+  struct GNUNET_CLIENT_TransmitHandle *th;
+
+  if (NULL != sock->th)
+    return NULL;
+  th = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_TransmitHandle));
+  th->sock = sock;
+  th->size = size;
+  th->timeout = GNUNET_TIME_relative_to_absolute (timeout);
+  th->auto_retry = auto_retry;
+  th->notify = notify;
+  th->notify_cls = notify_cls;
+  th->attempts_left = MAX_ATTEMPTS;
+  th->th = GNUNET_CONNECTION_notify_transmit_ready (sock->sock,
+                                                    size,
+                                                    timeout,
+                                                    &client_notify, th);
+  if (NULL == th->th)
+    {
+      GNUNET_break (0);
+      GNUNET_free (th);
+      return NULL;
+    }
+  sock->th = th;
+  return th;
 }
 
 
 /**
- * Context for processing 
- * "GNUNET_CLIENT_transmit_and_get_response" requests.
+ * Cancel a request for notification.
+ * 
+ * @param th handle from the original request.
  */
-struct TARCtx
+void
+GNUNET_CLIENT_notify_transmit_ready_cancel (struct
+                                            GNUNET_CLIENT_TransmitHandle *th)
 {
-  /**
-   * Client handle.
-   */
-  struct GNUNET_CLIENT_Connection *sock;
-
-  /**
-   * Message to transmit; do not free, allocated
-   * right after this struct.
-   */
-  const struct GNUNET_MessageHeader *hdr;
-
-  /**
-   * Timeout to use.
-   */
-  struct GNUNET_TIME_Absolute timeout;
-
-  /**
-   * Function to call when done.
-   */
-  GNUNET_CLIENT_MessageHandler rn;
-
-  /**
-   * Closure for "rn".
-   */
-  void *rn_cls;
-};
+  if (th->reconnect_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_break (NULL == th->th);
+      GNUNET_SCHEDULER_cancel (th->reconnect_task);
+      th->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  else
+    {
+      GNUNET_assert (NULL != th->th);
+      GNUNET_CONNECTION_notify_transmit_ready_cancel (th->th);
+    }
+  th->sock->th = NULL;
+  GNUNET_free (th);
+}
 
 
 /**
@@ -559,22 +942,25 @@ struct TARCtx
  * NULL and "size" zero if the socket was closed for
  * writing in the meantime.
  *
- * @param cls closure of type "struct TARCtx*"
+ * @param cls closure of type "struct TransmitGetResponseContext*"
  * @param size number of bytes available in buf
  * @param buf where the callee should write the message
  * @return number of bytes written to buf
  */
 static size_t
-transmit_for_response (void *cls,
-                      size_t size, 
-                      void *buf)
+transmit_for_response (void *cls, size_t size, void *buf)
 {
-  struct TARCtx *tc = cls;
+  struct TransmitGetResponseContext *tc = cls;
   uint16_t msize;
 
-  msize = ntohs(tc->hdr->size);
+  tc->sock->tag = NULL;
+  msize = ntohs (tc->hdr->size);
   if (NULL == buf)
     {
+#if DEBUG_CLIENT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 _("Could not submit request, not expecting to receive a response.\n"));
+#endif
       tc->rn (tc->rn_cls, NULL);
       GNUNET_free (tc);
       return 0;
@@ -582,9 +968,9 @@ transmit_for_response (void *cls,
   GNUNET_assert (size >= msize);
   memcpy (buf, tc->hdr, msize);
   GNUNET_CLIENT_receive (tc->sock,
-                        tc->rn,
-                        tc->rn_cls,
-                        GNUNET_TIME_absolute_get_remaining (tc->timeout));
+                         tc->rn,
+                         tc->rn_cls,
+                         GNUNET_TIME_absolute_get_remaining (tc->timeout));
   GNUNET_free (tc);
   return msize;
 }
@@ -601,32 +987,53 @@ transmit_for_response (void *cls,
  * @param hdr message to transmit
  * @param timeout when to give up (for both transmission
  *         and for waiting for a response)
+ * @param auto_retry if the connection to the service dies, should we
+ *        automatically re-connect and retry (within the timeout period)
+ *        or should we immediately fail in this case?  Pass GNUNET_YES
+ *        if the caller does not care about temporary connection errors,
+ *        for example because the protocol is stateless
  * @param rn function to call with the response
  * @param rn_cls closure for rn 
+ * @return GNUNET_OK on success, GNUNET_SYSERR if a request
+ *         is already pending
  */
-void
-GNUNET_CLIENT_transmit_and_get_response (struct GNUNET_CLIENT_Connection *sock,
-                                        const struct GNUNET_MessageHeader *hdr,
-                                        struct GNUNET_TIME_Relative timeout,
-                                        GNUNET_CLIENT_MessageHandler rn,
-                                        void *rn_cls)
+int
+GNUNET_CLIENT_transmit_and_get_response (struct GNUNET_CLIENT_Connection
+                                         *sock,
+                                         const struct GNUNET_MessageHeader
+                                         *hdr,
+                                         struct GNUNET_TIME_Relative timeout,
+                                         int auto_retry,
+                                         GNUNET_CLIENT_MessageHandler rn,
+                                         void *rn_cls)
 {
-  struct TARCtx *tc;
+  struct TransmitGetResponseContext *tc;
   uint16_t msize;
 
-  msize = ntohs(hdr->size);
-  tc = GNUNET_malloc(sizeof (struct TARCtx) + msize);
+  if (NULL != sock->th)
+    return GNUNET_SYSERR;
+  GNUNET_assert (sock->tag == NULL);
+  msize = ntohs (hdr->size);
+  tc = GNUNET_malloc (sizeof (struct TransmitGetResponseContext) + msize);
   tc->sock = sock;
-  tc->hdr = (const struct GNUNET_MessageHeader*) &tc[1]; 
+  tc->hdr = (const struct GNUNET_MessageHeader *) &tc[1];
   memcpy (&tc[1], hdr, msize);
   tc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
   tc->rn = rn;
   tc->rn_cls = rn_cls;
-  GNUNET_CLIENT_notify_transmit_ready (sock,
-                                      msize,
-                                      timeout,
-                                      &transmit_for_response,
-                                      tc);
+  if (NULL == GNUNET_CLIENT_notify_transmit_ready (sock,
+                                                   msize,
+                                                   timeout,
+                                                   auto_retry,
+                                                   &transmit_for_response,
+                                                   tc))
+    {
+      GNUNET_break (0);
+      GNUNET_free (tc);
+      return GNUNET_SYSERR;
+    }
+  sock->tag = tc;
+  return GNUNET_OK;
 }