asserts
[oweals/gnunet.git] / src / util / connection.c
index 066b81bbe76154321cb86d8487d481388adfba51..d2d4432c61c5d7f962d1e1078a7cea15ec1a8a1e 100644 (file)
@@ -244,6 +244,11 @@ struct GNUNET_CONNECTION_Handle
    */
   GNUNET_SCHEDULER_TaskIdentifier write_task;
 
+  /**
+   * Destroy task (if already scheduled).
+   */
+  GNUNET_SCHEDULER_TaskIdentifier destroy_task;
+
   /**
    * Handle to a pending DNS lookup request.
    */
@@ -269,13 +274,37 @@ struct GNUNET_CONNECTION_Handle
    */
   size_t max;
 
+  /**
+   * Ignore GNUNET_SCHEDULER_REASON_SHUTDOWN for this socket.
+   */
+  int ignore_shutdown;
+
   /**
    * Port to connect to.
    */
   uint16_t port;
 
+  /**
+   * When shutdown, do not ever actually close the socket, but
+   * free resources.  Only should ever be set if using program
+   * termination as a signal (because only then will the leaked
+   * socket be freed!)
+   */
+  int persist;
+
 };
 
+/**
+ * Set the persist option on this connection handle.  Indicates
+ * that the underlying socket or fd should never really be closed.
+ * Used for indicating process death.
+ *
+ * @param sock the connection to set persistent
+ */
+void GNUNET_CONNECTION_persist_(struct GNUNET_CONNECTION_Handle *sock)
+{
+  sock->persist = GNUNET_YES;
+}
 
 /**
  * Create a socket handle by boxing an existing OS socket.  The OS
@@ -420,17 +449,6 @@ GNUNET_CONNECTION_get_address (struct GNUNET_CONNECTION_Handle *sock,
 }
 
 
-/**
- * It is time to re-try connecting.
- * 
- * @param cls the handle for the connection that should be re-tried
- * @param tc unused scheduler taks context
- */
-static void
-retry_connect_continuation (void *cls,
-                            const struct GNUNET_SCHEDULER_TaskContext *tc);
-
-
 /**
  * This function is called after establishing a connection either has
  * succeeded or timed out.  Note that it is possible that the attempt
@@ -458,7 +476,9 @@ destroy_continuation (void *cls,
 {
   struct GNUNET_CONNECTION_Handle *sock = cls;
   GNUNET_CONNECTION_TransmitReadyNotify notify;
-
+  struct AddressProbe *pos;
+  
+  sock->destroy_task = GNUNET_SCHEDULER_NO_TASK;
   GNUNET_assert (sock->dns_active == NULL);
   if (0 != (sock->ccs & COCO_TRANSMIT_READY))
     {
@@ -475,11 +495,11 @@ destroy_continuation (void *cls,
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Destroy waits for write_task to be done (%p)\n", sock);
 #endif
-      GNUNET_SCHEDULER_add_after (sock->sched,
-                                  GNUNET_YES,
-                                  GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                  sock->write_task,
-                                  &destroy_continuation, sock);
+      GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == sock->destroy_task);
+      sock->destroy_task 
+       = GNUNET_SCHEDULER_add_after (sock->sched,
+                                     sock->write_task,
+                                     &destroy_continuation, sock);
       return;
     }
   if (0 != (sock->ccs & COCO_RECEIVE_AGAIN))
@@ -493,20 +513,34 @@ destroy_continuation (void *cls,
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Shutting down socket (%p)\n", sock);
 #endif
-      GNUNET_NETWORK_socket_shutdown (sock->sock, SHUT_RDWR);
+      if (sock->persist != GNUNET_YES)
+        GNUNET_NETWORK_socket_shutdown (sock->sock, SHUT_RDWR);
     }
   if (sock->read_task != GNUNET_SCHEDULER_NO_TASK)
     {
-      GNUNET_SCHEDULER_add_after (sock->sched,
-                                  GNUNET_YES,
-                                  GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                  sock->read_task,
-                                  &destroy_continuation, sock);
+      GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == sock->destroy_task);
+      sock->destroy_task 
+       = GNUNET_SCHEDULER_add_after (sock->sched,
+                                     sock->read_task,
+                                     &destroy_continuation, sock);
       return;
     }
 #if DEBUG_CONNECTION
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Destroy actually runs (%p)!\n", sock);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
+             "Destroy actually runs (%p)!\n", sock);
 #endif
+  if (sock->dns_active != NULL)
+    {
+      GNUNET_RESOLVER_request_cancel (sock->dns_active);
+      sock->dns_active = NULL;
+    }
+  while (NULL != (pos = sock->ap_head))
+    {
+      GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (pos->sock));
+      GNUNET_SCHEDULER_cancel (sock->sched, pos->task);
+      GNUNET_CONTAINER_DLL_remove (sock->ap_head, sock->ap_tail, pos);
+      GNUNET_free (pos);
+    }
   GNUNET_assert (sock->nth.timeout_task == GNUNET_SCHEDULER_NO_TASK);
   GNUNET_assert (sock->ccs == COCO_NONE);
   if (NULL != (notify = sock->nth.notify_ready))
@@ -514,10 +548,18 @@ destroy_continuation (void *cls,
       sock->nth.notify_ready = NULL;
       notify (sock->nth.notify_ready_cls, 0, NULL);
     }
-  if (sock->sock != NULL)
-    GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock->sock));
+
+  if (sock->sock != NULL) 
+    {
+      if (sock->persist != GNUNET_YES)
+       GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock->sock));
+      else
+       GNUNET_free (sock->sock); /* at least no memory leak (we deliberately
+                                    leak the socket in this special case) ... */
+    }
   GNUNET_free_non_null (sock->addr);
   GNUNET_free_non_null (sock->hostname);
+  GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == sock->destroy_task);
 #if DEBUG_CONNECTION
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Freeing memory of connection %p.\n", sock);
@@ -558,7 +600,6 @@ connect_fail_continuation (struct GNUNET_CONNECTION_Handle *h)
   GNUNET_break (h->dns_active == GNUNET_NO);
   GNUNET_break (h->sock == NULL);
 
-  /* FIXME: trigger delayed reconnect attempt... */
   /* trigger jobs that used to wait on "connect_task" */
   if (0 != (h->ccs & COCO_RECEIVE_AGAIN))
     {
@@ -569,8 +610,6 @@ connect_fail_continuation (struct GNUNET_CONNECTION_Handle *h)
 #endif
       h->ccs -= COCO_RECEIVE_AGAIN;
       h->read_task = GNUNET_SCHEDULER_add_after (h->sched,
-                                                 GNUNET_NO,
-                                                 GNUNET_SCHEDULER_PRIORITY_KEEP,
                                                  GNUNET_SCHEDULER_NO_TASK,
                                                  &receive_again, h);
     }
@@ -586,8 +625,6 @@ connect_fail_continuation (struct GNUNET_CONNECTION_Handle *h)
       h->nth.timeout_task = GNUNET_SCHEDULER_NO_TASK;
       h->ccs -= COCO_TRANSMIT_READY;
       h->write_task = GNUNET_SCHEDULER_add_after (h->sched,
-                                                  GNUNET_NO,
-                                                  GNUNET_SCHEDULER_PRIORITY_KEEP,
                                                   GNUNET_SCHEDULER_NO_TASK,
                                                   &transmit_ready, h);
     }
@@ -599,10 +636,11 @@ connect_fail_continuation (struct GNUNET_CONNECTION_Handle *h)
                   h);
 #endif
       h->ccs -= COCO_DESTROY_CONTINUATION;
-      GNUNET_SCHEDULER_add_continuation (h->sched,
-                                         GNUNET_NO,
-                                         &destroy_continuation,
-                                         h, GNUNET_SCHEDULER_REASON_TIMEOUT);
+      GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->destroy_task);
+      h->destroy_task
+       = GNUNET_SCHEDULER_add_now (h->sched,
+                                   &destroy_continuation,
+                                   h);
     }
 }
 
@@ -630,8 +668,6 @@ connect_success_continuation (struct GNUNET_CONNECTION_Handle *h)
 #endif
       h->ccs -= COCO_RECEIVE_AGAIN;
       h->read_task = GNUNET_SCHEDULER_add_after (h->sched,
-                                                 GNUNET_NO,
-                                                 GNUNET_SCHEDULER_PRIORITY_KEEP,
                                                  GNUNET_SCHEDULER_NO_TASK,
                                                  &receive_again, h);
     }
@@ -648,9 +684,6 @@ connect_success_continuation (struct GNUNET_CONNECTION_Handle *h)
       h->ccs -= COCO_TRANSMIT_READY;
       h->write_task =
         GNUNET_SCHEDULER_add_write_net (h->sched,
-                                        GNUNET_NO,
-                                        GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                        GNUNET_SCHEDULER_NO_TASK,
                                         GNUNET_TIME_absolute_get_remaining
                                         (h->nth.transmit_timeout), h->sock,
                                         &transmit_ready, h);
@@ -663,11 +696,11 @@ connect_success_continuation (struct GNUNET_CONNECTION_Handle *h)
                   h);
 #endif
       h->ccs -= COCO_DESTROY_CONTINUATION;
-      GNUNET_SCHEDULER_add_continuation (h->sched,
-                                         GNUNET_NO,
-                                         &destroy_continuation,
-                                         h,
-                                         GNUNET_SCHEDULER_REASON_PREREQ_DONE);
+      GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->destroy_task);
+      h->destroy_task
+       = GNUNET_SCHEDULER_add_now (h->sched,
+                                   &destroy_continuation,
+                                   h);
     }
 }
 
@@ -705,6 +738,8 @@ connect_probe_continuation (void *cls,
         connect_fail_continuation (h);
       return;
     }
+  GNUNET_assert (h->sock == NULL);
+  GNUNET_assert (ap->sock != NULL);
   h->sock = ap->sock;
   GNUNET_assert (h->addr == NULL);
   h->addr = GNUNET_malloc (ap->addrlen);
@@ -748,6 +783,7 @@ try_connect_using_address (void *cls,
     }
   if (h->sock != NULL)
     return;                     /* already connected */
+  GNUNET_assert (h->addr == NULL);
   /* try to connect */
 #if DEBUG_CONNECTION
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -800,43 +836,18 @@ try_connect_using_address (void *cls,
   delay = GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT;
   if (h->nth.notify_ready != NULL)
     delay = GNUNET_TIME_relative_min (delay,
-                                      GNUNET_TIME_absolute_get_remaining (h->
-                                                                          nth.
-                                                                          transmit_timeout));
+                                      GNUNET_TIME_absolute_get_remaining
+                                      (h->nth.transmit_timeout));
   if (h->receiver != NULL)
     delay = GNUNET_TIME_relative_min (delay,
-                                      GNUNET_TIME_absolute_get_remaining (h->
-                                                                          receive_timeout));
+                                      GNUNET_TIME_absolute_get_remaining
+                                      (h->receive_timeout));
   ap->task =
-    GNUNET_SCHEDULER_add_write_net (h->sched, GNUNET_NO,
-                                    GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                    GNUNET_SCHEDULER_NO_TASK, delay, ap->sock,
+    GNUNET_SCHEDULER_add_write_net (h->sched, delay, ap->sock,
                                     &connect_probe_continuation, ap);
 }
 
 
-/**
- * It is time to re-try connecting.
- * 
- * @param cls the handle for the connection that should be re-tried
- * @param tc unused scheduler taks context
- */
-static void
-retry_connect_continuation (void *cls,
-                            const struct GNUNET_SCHEDULER_TaskContext *tc)
-{
-  struct GNUNET_CONNECTION_Handle *sock = cls;
-
-  GNUNET_assert (sock->dns_active == NULL);
-  sock->dns_active = GNUNET_RESOLVER_ip_get (sock->sched,
-                                            sock->cfg,
-                                            sock->hostname,
-                                            AF_UNSPEC,
-                                            GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT,
-                                            &try_connect_using_address, sock);
-}
-
-
 /**
  * Create a socket handle by (asynchronously) connecting to a host.
  * This function returns immediately, even if the connection has not
@@ -867,7 +878,12 @@ GNUNET_CONNECTION_create_from_connect (struct GNUNET_SCHEDULER_Handle *sched,
   ret->write_buffer_size = maxbuf;
   ret->port = port;
   ret->hostname = GNUNET_strdup (hostname);
-  retry_connect_continuation (ret, NULL);
+  ret->dns_active = GNUNET_RESOLVER_ip_get (sched,
+                                            cfg,
+                                            ret->hostname,
+                                            AF_UNSPEC,
+                                            GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT,
+                                            &try_connect_using_address, ret);
   return ret;
 }
 
@@ -941,25 +957,41 @@ GNUNET_CONNECTION_check (struct GNUNET_CONNECTION_Handle *sock)
 
 /**
  * Close the socket and free associated resources. Pending
- * transmissions are simply dropped.  A pending receive call will be
- * called with an error code of "EPIPE".
+ * transmissions may be completed or dropped depending on the
+ * arguments.   If a receive call is pending and should 
+ * NOT be completed, 'GNUNET_CONNECTION_receive_cancel'
+ * should be called explicitly first.
  *
  * @param sock socket to destroy
+ * @param finish_pending_write should pending writes be completed or aborted?
+ *        (this applies to transmissions where the data has already been
+ *        read from the application; all other transmissions should be
+ *        aborted using 'GNUNET_CONNECTION_notify_transmit_ready_cancel').
  */
 void
-GNUNET_CONNECTION_destroy (struct GNUNET_CONNECTION_Handle *sock)
+GNUNET_CONNECTION_destroy (struct GNUNET_CONNECTION_Handle *sock,
+                          int finish_pending_write)
 {
+  if (GNUNET_NO == finish_pending_write)
+    {
+      if (sock->write_task != GNUNET_SCHEDULER_NO_TASK)
+       {
+         GNUNET_SCHEDULER_cancel (sock->sched,
+                                  sock->write_task);
+         sock->write_task = GNUNET_SCHEDULER_NO_TASK;
+         sock->write_buffer_off = 0;
+       }
+    }
   if ((sock->write_buffer_off == 0) && (sock->dns_active != NULL))
     {
       GNUNET_RESOLVER_request_cancel (sock->dns_active);
       sock->dns_active = NULL;
     }
   GNUNET_assert (sock->sched != NULL);
-  GNUNET_SCHEDULER_add_after (sock->sched,
-                              GNUNET_YES,
-                              GNUNET_SCHEDULER_PRIORITY_KEEP,
-                              GNUNET_SCHEDULER_NO_TASK,
-                              &destroy_continuation, sock);
+  GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == sock->destroy_task);
+  sock->destroy_task 
+    = GNUNET_SCHEDULER_add_now (sock->sched,
+                               &destroy_continuation, sock);
 }
 
 
@@ -1008,6 +1040,21 @@ receive_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
   GNUNET_CONNECTION_Receiver receiver;
 
   sh->read_task = GNUNET_SCHEDULER_NO_TASK;
+  if ( (GNUNET_YES == sh->ignore_shutdown) &&
+       (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))) 
+    {
+      /* ignore shutdown request, go again immediately */
+#if DEBUG_CONNECTION
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Ignoring shutdown signal per configuration\n");
+#endif
+      sh->read_task = GNUNET_SCHEDULER_add_read_net (tc->sched,
+                                                    GNUNET_TIME_absolute_get_remaining
+                                                    (sh->receive_timeout),
+                                                    sh->sock,
+                                                    &receive_ready, sh);
+      return;
+    }
   now = GNUNET_TIME_absolute_get ();
   if ((now.value > sh->receive_timeout.value) ||
       (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT)) ||
@@ -1101,9 +1148,6 @@ receive_again (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
   GNUNET_assert (sh->sock != NULL);
   /* connect succeeded, wait for data! */
   sh->read_task = GNUNET_SCHEDULER_add_read_net (tc->sched,
-                                                 GNUNET_YES,
-                                                 GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                 GNUNET_SCHEDULER_NO_TASK,
                                                  GNUNET_TIME_absolute_get_remaining
                                                  (sh->receive_timeout),
                                                  sh->sock,
@@ -1157,6 +1201,20 @@ GNUNET_CONNECTION_receive (struct GNUNET_CONNECTION_Handle *sock,
 }
 
 
+/**
+ * Configure this connection to ignore shutdown signals.
+ *
+ * @param sock socket handle
+ * @param do_ignore GNUNET_YES to ignore, GNUNET_NO to restore default
+ */
+void
+GNUNET_CONNECTION_ignore_shutdown (struct GNUNET_CONNECTION_Handle *sock,
+                                  int do_ignore)
+{
+  sock->ignore_shutdown = do_ignore;
+}
+
+
 /**
  * Cancel receive job on the given socket.  Note that the
  * receiver callback must not have been called yet in order
@@ -1318,6 +1376,20 @@ transmit_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
   GNUNET_assert (sock->write_task != GNUNET_SCHEDULER_NO_TASK);
   sock->write_task = GNUNET_SCHEDULER_NO_TASK;
   GNUNET_assert (sock->nth.timeout_task == GNUNET_SCHEDULER_NO_TASK);
+  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) 
+    {
+      if (sock->ignore_shutdown == GNUNET_YES)
+       goto SCHEDULE_WRITE;    /* ignore shutdown, go again immediately */
+#if DEBUG_CONNECTION
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Transmit to `%s' fails, shutdown happened (%p).\n",
+                  GNUNET_a2s (sock->addr, sock->addrlen), sock);
+#endif
+      notify = sock->nth.notify_ready;
+      sock->nth.notify_ready = NULL;
+      notify (sock->nth.notify_ready_cls, 0, NULL);
+      return;
+    }
   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT))
     {
 #if DEBUG_CONNECTION
@@ -1364,6 +1436,9 @@ transmit_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
       /* no data ready for writing, terminate write loop */
       return;
     }
+  GNUNET_assert (have <= sock->write_buffer_size);
+  GNUNET_assert (have + sock->write_buffer_pos <= sock->write_buffer_size);
+  GNUNET_assert (sock->write_buffer_pos <= sock->write_buffer_size);
 RETRY:
   ret = GNUNET_NETWORK_socket_send (sock->sock,
                                     &sock->write_buffer[sock->
@@ -1376,9 +1451,7 @@ RETRY:
 #if DEBUG_CONNECTION
       GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, "send");
 #endif
-      GNUNET_NETWORK_socket_shutdown (sock->sock, SHUT_RDWR);
-      GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (sock->sock));
-      sock->sock = NULL;
+      GNUNET_NETWORK_socket_shutdown (sock->sock, SHUT_WR);
       transmit_error (sock);
       return;
     }
@@ -1406,9 +1479,6 @@ SCHEDULE_WRITE:
   if (sock->write_task == GNUNET_SCHEDULER_NO_TASK)
     sock->write_task =
       GNUNET_SCHEDULER_add_write_net (tc->sched,
-                                      GNUNET_NO,
-                                      GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                      GNUNET_SCHEDULER_NO_TASK,
                                       GNUNET_TIME_absolute_get_remaining
                                       (sock->nth.transmit_timeout),
                                       sock->sock, &transmit_ready, sock);
@@ -1452,12 +1522,8 @@ GNUNET_CONNECTION_notify_transmit_ready (struct GNUNET_CONNECTION_Handle
   if ((sock->sock == NULL) &&
       (sock->ap_head == NULL) && (sock->dns_active == NULL))
     {
-      sock->write_task = GNUNET_SCHEDULER_add_delayed (sock->sched,
-                                                       GNUNET_NO,
-                                                       GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                       GNUNET_SCHEDULER_NO_TASK,
-                                                       GNUNET_TIME_UNIT_ZERO,
-                                                       &connect_error, sock);
+      sock->write_task = GNUNET_SCHEDULER_add_now (sock->sched,
+                                                  &connect_error, sock);
       return &sock->nth;
     }
   if (GNUNET_SCHEDULER_NO_TASK != sock->write_task)
@@ -1469,9 +1535,6 @@ GNUNET_CONNECTION_notify_transmit_ready (struct GNUNET_CONNECTION_Handle
                   "Scheduling transmit_ready (%p).\n", sock);
 #endif
       sock->write_task = GNUNET_SCHEDULER_add_write_net (sock->sched,
-                                                         GNUNET_NO,
-                                                         GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                         GNUNET_SCHEDULER_NO_TASK,
                                                          GNUNET_TIME_absolute_get_remaining
                                                          (sock->nth.
                                                           transmit_timeout),
@@ -1488,9 +1551,6 @@ GNUNET_CONNECTION_notify_transmit_ready (struct GNUNET_CONNECTION_Handle
 #endif
       sock->ccs |= COCO_TRANSMIT_READY;
       sock->nth.timeout_task = GNUNET_SCHEDULER_add_delayed (sock->sched,
-                                                             GNUNET_NO,
-                                                             GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                             GNUNET_SCHEDULER_NO_TASK,
                                                              timeout,
                                                              &transmit_timeout,
                                                              sock);
@@ -1522,8 +1582,11 @@ GNUNET_CONNECTION_notify_transmit_ready_cancel (struct
     }
   else
     {
-      GNUNET_SCHEDULER_cancel (h->sh->sched, h->sh->write_task);
-      h->sh->write_task = GNUNET_SCHEDULER_NO_TASK;
+      if (h->sh->write_task != GNUNET_SCHEDULER_NO_TASK)
+       {
+         GNUNET_SCHEDULER_cancel (h->sh->sched, h->sh->write_task);
+         h->sh->write_task = GNUNET_SCHEDULER_NO_TASK;
+       }
     }
   h->notify_ready = NULL;
 }