gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libmicrohttpd] branch master updated: mhd_send.c: partial re-write, fix


From: gnunet
Subject: [libmicrohttpd] branch master updated: mhd_send.c: partial re-write, fixed, portability
Date: Fri, 11 Dec 2020 18:25:37 +0100

This is an automated email from the git hooks/post-receive script.

karlson2k pushed a commit to branch master
in repository libmicrohttpd.

The following commit(s) were added to refs/heads/master by this push:
     new 9e45e248 mhd_send.c: partial re-write, fixed, portability
9e45e248 is described below

commit 9e45e2486f4e24552439cef43d61d841229a8fea
Author: Evgeny Grin (Karlson2k) <k2k@narod.ru>
AuthorDate: Fri Dec 11 20:24:55 2020 +0300

    mhd_send.c: partial re-write, fixed, portability
    
    Full re-write of sockets buffering and data pushing.
    Fixed portability for non-Linux systems.
    Lowered number of additional sys-calls.
    Added workaround logic if main buffer/data push calls failed.
---
 src/microhttpd/mhd_send.c | 709 ++++++++++++++++++++++++++++++++++++----------
 src/microhttpd/mhd_send.h |   4 +-
 2 files changed, 565 insertions(+), 148 deletions(-)

diff --git a/src/microhttpd/mhd_send.c b/src/microhttpd/mhd_send.c
index eab145bc..018ba657 100644
--- a/src/microhttpd/mhd_send.c
+++ b/src/microhttpd/mhd_send.c
@@ -1,6 +1,8 @@
 /*
   This file is part of libmicrohttpd
-  Copyright (C) 2019 ng0 <ng0@n0.is>
+  Copyright (C) 2017,2020 Karlson2k (Evgeny Grin), Full re-write of buffering 
and
+                     pushing, many bugs fixes, optimisations, sendfile() 
porting
+  Copyright (C) 2019 ng0 <ng0@n0.is>, Initial version of send() wrappers
 
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
@@ -21,9 +23,9 @@
 /**
  * @file microhttpd/mhd_send.c
  * @brief Implementation of send() wrappers.
+ * @author Karlson2k (Evgeny Grin)
  * @author ng0 (N. Gillmann)
  * @author Christian Grothoff
- * @author Evgeny Grin
  */
 
 /* Worth considering for future improvements and additions:
@@ -32,10 +34,6 @@
  * large a chunk as possible to the socket. Alternatively,
  * use madvise(..., MADV_SEQUENTIAL). */
 
-/* Functions to be used in: send_param_adapter, MHD_send_
- * and every place where sendfile(), sendfile64(), setsockopt()
- * are used. */
-
 #include "mhd_send.h"
 #ifdef MHD_LINUX_SOLARIS_SENDFILE
 #include <sys/sendfile.h>
@@ -51,6 +49,7 @@
 #endif /* HAVE_SYS_PARAM_H */
 #include "mhd_assert.h"
 
+#include "mhd_limits.h"
 
 /**
  * sendfile() chuck size
@@ -106,6 +105,121 @@ MHD_send_init_static_vars_ (void)
 #endif /* HAVE_FREEBSD_SENDFILE */
 
 
+/**
+ * Set required TCP_NODELAY state for connection socket
+ *
+ * The function automatically updates sk_nodelay state.
+ * @param connection the connection to manipulate
+ * @param nodelay_state the requested new state of socket
+ * @return true if succeed, false if failed
+ */
+static bool
+connection_set_nodelay_state_ (struct MHD_Connection *connection,
+                               bool nodelay_state)
+{
+  const MHD_SCKT_OPT_BOOL_ off_val = 0;
+  const MHD_SCKT_OPT_BOOL_ on_val = 1;
+  int err_code;
+
+  if (0 == setsockopt (connection->socket_fd,
+                       IPPROTO_TCP,
+                       TCP_NODELAY,
+                       (const void *) (nodelay_state ? &on_val : &off_val),
+                       sizeof (off_val)))
+  {
+    connection->sk_nodelay = nodelay_state;
+    return true;
+  }
+  err_code = MHD_socket_get_error_ ();
+  switch (err_code)
+  {
+  case ENOTSOCK:
+    /* FIXME: Could be we are talking to a pipe, maybe remember this
+       and avoid all setsockopt() in the future? */
+    break;
+  case EBADF:
+  /* FIXME: should we die hard here? */
+  case EINVAL:
+  /* FIXME: optlen invalid, should at least log this, maybe die */
+  case EFAULT:
+  /* wopsie, should at least log this, FIXME: maybe die */
+  case ENOPROTOOPT:
+  /* optlen unknown, should at least log this */
+  default:
+#ifdef HAVE_MESSAGES
+    MHD_DLOG (connection->daemon,
+              _ ("Setting %s option to %s state failed: %s\n"),
+              "TCP_NODELAY",
+              nodelay_state ? _ ("ON") : _ ("OFF"),
+              MHD_socket_strerr_ (err_code));
+#endif /* HAVE_MESSAGES */
+    break;
+  }
+  return false;
+}
+
+
+#if defined(MHD_TCP_CORK_NOPUSH)
+/**
+ * Set required cork state for connection socket
+ *
+ * The function automatically updates sk_corked state.
+ * @param connection the connection to manipulate
+ * @param cork_state the requested new state of socket
+ * @return true if succeed, false if failed
+ */
+static bool
+connection_set_cork_state_ (struct MHD_Connection *connection,
+                            bool cork_state)
+{
+  const MHD_SCKT_OPT_BOOL_ off_val = 0;
+  const MHD_SCKT_OPT_BOOL_ on_val = 1;
+  int err_code;
+
+  if (0 == setsockopt (connection->socket_fd,
+                       IPPROTO_TCP,
+                       MHD_TCP_CORK_NOPUSH,
+                       (const void *) (cork_state ? &on_val : &off_val),
+                       sizeof (off_val)))
+  {
+    connection->sk_corked = cork_state;
+    return true;
+  }
+  err_code = MHD_socket_get_error_ ();
+  switch (err_code)
+  {
+  case ENOTSOCK:
+    /* FIXME: Could be we are talking to a pipe, maybe remember this
+       and avoid all setsockopt() in the future? */
+    break;
+  case EBADF:
+  /* FIXME: should we die hard here? */
+  case EINVAL:
+  /* FIXME: optlen invalid, should at least log this, maybe die */
+  case EFAULT:
+  /* wopsie, should at least log this, FIXME: maybe die */
+  case ENOPROTOOPT:
+  /* optlen unknown, should at least log this */
+  default:
+#ifdef HAVE_MESSAGES
+    MHD_DLOG (connection->daemon,
+              _ ("Setting %s option to %s state failed: %s\n"),
+#ifdef TCP_CORK
+              "TCP_CORK",
+#else  /* ! TCP_CORK */
+              "TCP_NOPUSH",
+#endif /* ! TCP_CORK */
+              cork_state ? _ ("ON") : _ ("OFF"),
+              MHD_socket_strerr_ (err_code));
+#endif /* HAVE_MESSAGES */
+    break;
+  }
+  return false;
+}
+
+
+#endif /* MHD_TCP_CORK_NOPUSH */
+
 /**
  * Handle pre-send setsockopt calls.
  *
@@ -126,96 +240,259 @@ pre_send_setopt (struct MHD_Connection *connection,
    * Final piece is indicated by push_data == true. */
   const bool buffer_data = (! push_data);
 
-#ifdef MHD_USE_MSG_MORE
-  if (plain_send)
+  /* The goal is to minimise the total number of additional sys-calls
+   * before and after send().
+   * The following tricky (over-)complicated algorithm typically use zero,
+   * one or two additional sys-calls (depending on OS) for each response. */
+
+  if (buffer_data)
   {
-    /* MSG_MORE is used, no need for extra syscalls! */
-    return;
-  }
+    /* Need to buffer data if possible. */
+#ifdef MHD_USE_MSG_MORE
+    if (plain_send)
+      return; /* Data is buffered by send() with MSG_MORE flag.
+               * No need to check or change anything. */
 #else  /* ! MHD_USE_MSG_MORE */
-  (void) plain_send; /* Mute compiler warning. */
+    (void) plain_send; /* Mute compiler warning. */
 #endif /* ! MHD_USE_MSG_MORE */
 
-#if defined(MHD_TCP_CORK_NOPUSH)
-  /* If connection is already in required corked state, do nothing. */
-  if (connection->sk_corked == buffer_data)
-    return;
-  if (push_data)
-    return; /* nothing to do *pre* syscall! BUG: to be fixed */
-  ret = MHD_socket_cork_ (connection->socket_fd,
-                          buffer_data);
-  if (0 != ret)
-  {
-    connection->sk_corked = buffer_data;
+#ifdef MHD_TCP_CORK_NOPUSH
+    if (_MHD_ON == connection->sk_corked)
+      return; /* The connection was already corked. */
+
+    if (connection_set_cork_state_ (connection, true))
+      return; /* The connection has been corked. */
+
+    /* Failed to cork the connection.
+     * Really unlikely to happen on TCP connections. */
+#endif /* MHD_TCP_CORK_NOPUSH */
+    if (_MHD_OFF == connection->sk_nodelay)
+      return; /* TCP_NODELAY was not set for the socket.
+               * Nagle's algorithm will buffer some data. */
+
+    /* Try to reset TCP_NODELAY state for the socket.
+     * Ignore possible error as no other options exist to
+     * buffer data. */
+    connection_set_nodelay_state_ (connection, false);
+    /* TCP_NODELAY has been (hopefully) reset for the socket.
+     * Nagle's algorithm will buffer some data. */
     return;
   }
-  switch (errno)
-  {
-  case ENOTSOCK:
-    /* FIXME: Could be we are talking to a pipe, maybe remember this
-       and avoid all setsockopt() in the future? */
-    break;
-  case EBADF:
-    /* FIXME: should we die hard here? */
-    break;
-  case EINVAL:
-    /* FIXME: optlen invalid, should at least log this, maybe die */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ ("optlen invalid: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  case EFAULT:
-    /* wopsie, should at least log this, FIXME: maybe die */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ (
-                "The address pointed to by optval is not a valid part of the 
process address space: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  case ENOPROTOOPT:
-    /* optlen unknown, should at least log this */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ ("The option is unknown: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  default:
-    /* any others? man page does not list more... */
-    break;
-  }
-#else
-  /* CORK/NOPUSH do not exist on this platform,
-     Turning on/off of Naggle's algorithm
-     (otherwise we keep it always off) */
-  if (connection->sk_nodelay == push_data)
+
+  /* Need to push data after send() */
+  /* Prefer to make additional sys-call after the send()
+   * as the next send() may consume only part of the
+   * prepared data and additional send() may be required. */
+#ifdef MHD_TCP_CORK_NOPUSH
+#ifdef _MHD_CORK_RESET_PUSH_DATA
+#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
+  /* Data can be pushed immediately by uncorking socket regardless of
+   * cork state before. */
+  /* This is typical for Linux, no other kernel with
+   * such behavior are known so far. */
+
+  /* No need to check the current state of TCP_CORK / TCP_NOPUSH
+   * as reset of cork will push the data anyway. */
+  return; /* Data may be pushed by resetting of
+           * TCP_CORK / TCP_NOPUSH after send() */
+#else  /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
+  /* Reset of TCP_CORK / TCP_NOPUSH will push the data
+   * only if socket is corked. */
+
+#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS
+  /* Data can be pushed immediately by setting TCP_NODELAY regardless
+   * of TCP_NODDELAY or corking state before. */
+
+  /* Dead code currently, no known kernels with such behavior. */
+  return; /* Data may be pushed by setting of TCP_NODELAY after send().
+             No need to make extra sys-calls before send().*/
+#else  /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
+
+#ifdef _MHD_NODELAY_SET_PUSH_DATA
+  /* Setting of TCP_NODELAY will push the data only if
+   * both TCP_NODELAY and TCP_CORK / TCP_NOPUSH were not set. */
+
+  /* Data can be pushed immediately by uncorking socket if
+   * socket was corked before or by setting TCP_NODELAY if
+   * socket was not corked and TCP_NODELAY was not set before. */
+
+  /* Dead code currently as Linux is the only kernel that push
+   * data by setting of TCP_NODELAY and Linux push data always. */
+#else  /* ! _MHD_NODELAY_SET_PUSH_DATA */
+  /* Data can be pushed immediately by uncorking socket or
+   * can be pushed by send() on uncorked socket if
+   * TCP_NODELAY was set *before*. */
+
+  /* This is typical FreeBSD behavior. */
+#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */
+
+  if (_MHD_ON == connection->sk_corked)
+    return; /* Socket is corked. Data can be pushed by resetting of
+             * TCP_CORK / TCP_NOPUSH after send() */
+  else if (_MHD_OFF == connection->sk_corked)
   {
-    /* nothing to do, success! */
-    return;
+    /* The socket is not corked. */
+    if (_MHD_ON == connection->sk_nodelay)
+      return; /* TCP_NODELAY was already set,
+               * data will be pushed automatically by the next send() */
+#ifdef _MHD_NODELAY_SET_PUSH_DATA
+    else if (_MHD_UNKNOWN == connection->sk_nodelay)
+    {
+      /* Setting TCP_NODELAY may push data.
+       * Cork socket here and uncork after send(). */
+      if (connection_set_cork_state_ (connection, true))
+        return; /* The connection has been corked.
+                 * Data can be pushed by resetting of
+                 * TCP_CORK / TCP_NOPUSH after send() */
+      else
+      {
+        /* The socket cannot be corked.
+         * Really unlikely to happen on TCP connections */
+        /* Have to set TCP_NODELAY.
+         * If TCP_NODELAY real system state was OFF then
+         * already buffered data may be pushed here, but this is unlikely
+         * to happen as it is only a backup solution when corking has failed.
+         * Ignore possible error here as no other options exist to
+         * push data. */
+        connection_set_nodelay_state_ (connection, true);
+        /* TCP_NODELAY has been (hopefully) set for the socket.
+         * The data will be pushed by the next send(). */
+        return;
+      }
+    }
+#endif /* _MHD_NODELAY_SET_PUSH_DATA */
+    else
+    {
+#ifdef _MHD_NODELAY_SET_PUSH_DATA
+      /* TCP_NODELAY was switched off and
+       * the socket is not corked. */
+#else  /* ! _MHD_NODELAY_SET_PUSH_DATA */
+      /* Socket is not corked and TCP_NODELAY was not set or unknown. */
+#endif /* ! _MHD_NODELAY_SET_PUSH_DATA */
+
+      /* At least one additional sys-call is required. */
+      /* Setting TCP_NODELAY is optimal here as data will be pushed
+       * automatically by the next send() and no additional
+       * sys-call are needed after the send(). */
+      if (connection_set_nodelay_state_ (connection, true))
+        return;
+      else
+      {
+        /* Failed to set TCP_NODELAY for the socket.
+         * Really unlikely to happen on TCP connections. */
+        /* Cork the socket here and make additional sys-call
+         * to uncork the socket after send(). */
+        /* Ignore possible error here as no other options exist to
+         * push data. */
+        connection_set_cork_state_ (connection, true);
+        /* The connection has been (hopefully) corked.
+         * Data can be pushed by resetting of TCP_CORK / TCP_NOPUSH
+         * after send() */
+        return;
+      }
+    }
   }
-  if (0 == MHD_socket_set_nodelay_ (connection->socket_fd,
-                                    (push_data)))
-    connection->sk_nodelay = push_data;
-#endif
+  /* Corked state is unknown. Need to make sys-call here otherwise
+   * data may not be pushed. */
+  if (connection_set_cork_state_ (connection, true))
+    return; /* The connection has been corked.
+             * Data can be pushed by resetting of
+             * TCP_CORK / TCP_NOPUSH after send() */
+  /* The socket cannot be corked.
+   * Really unlikely to happen on TCP connections */
+  if (_MHD_ON == connection->sk_nodelay)
+    return; /* TCP_NODELAY was already set,
+             * data will be pushed by the next send() */
+  /* Have to set TCP_NODELAY. */
+#ifdef _MHD_NODELAY_SET_PUSH_DATA
+  /* If TCP_NODELAY state was unknown (external connection) then
+   * already buffered data may be pushed here, but this is unlikely
+   * to happen as it is only a backup solution when corking has failed. */
+#endif /* _MHD_NODELAY_SET_PUSH_DATA */
+  /* Ignore possible error here as no other options exist to
+   * push data. */
+  connection_set_nodelay_state_ (connection, true);
+  /* TCP_NODELAY has been (hopefully) set for the socket.
+   * The data will be pushed by the next send(). */
+  return;
+#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
+#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
+#else  /* ! _MHD_CORK_RESET_PUSH_DATA */
+  /* Neither uncorking the socket or setting TCP_NODELAY
+   * push the data immediately. */
+  /* The only way to push the data is to use send() on uncorked
+   * socket with TCP_NODELAY switched on . */
+
+  /* This is a typical *BSD (except FreeBSD) and Darwin behavior. */
+
+  /* Uncork socket if socket wasn't uncorked. */
+  if (_MHD_OFF != connection->sk_corked)
+    connection_set_cork_state_ (connection, false);
+
+  /* Set TCP_NODELAY if it wasn't set. */
+  if (_MHD_ON != connection->sk_nodelay)
+    connection_set_nodelay_state_ (connection, true);
+
+  return;
+#endif /* ! _MHD_CORK_RESET_PUSH_DATA */
+#else  /* ! MHD_TCP_CORK_NOPUSH */
+  /* Buffering of data is controlled only by
+   * Nagel's algorithm. */
+  /* Set TCP_NODELAY if it wasn't set. */
+  if (_MHD_ON != connection->sk_nodelay)
+    connection_set_nodelay_state_ (connection, true);
+#endif /* ! MHD_TCP_CORK_NOPUSH */
+}
+
+
+#ifndef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
+/**
+ * Send zero-sized data
+ *
+ * This function use send of zero-sized data to kick data from the socket
+ * buffers to the network. The socket must not be corked and must have
+ * TCP_NODELAY switched on.
+ * Used only as last resort option, when other options are failed due to
+ * some errors.
+ * Should not be called on typical data processing.
+ * @return true if succeed, false if failed
+ */
+static bool
+zero_send_ (struct MHD_Connection *connection)
+{
+  int dummy;
+  mhd_assert (_MHD_OFF == connection->sk_corked);
+  mhd_assert (_MHD_ON == connection->sk_nodelay);
+
+  dummy = 0; /* Mute compiler and analyzer warnings */
+
+  if (0 == MHD_send_ (connection->socket_fd, &dummy, 0))
+    return true;
+
+#ifdef HAVE_MESSAGES
+  MHD_DLOG (connection->daemon,
+            _ ("Zero-send failed: %s\n"),
+            MHD_socket_last_strerr_ () );
+#endif /* HAVE_MESSAGES */
+  return false;
 }
 
 
+#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
+
 /**
  * Handle post-send setsockopt calls.
  *
  * @param connection the MHD_Connection structure
- * @param plain_send set to true if plain send() or sendmsg() have been
- *                   called,
- *                   set to false if TLS socket send(), sendfile() or
- *                   writev() has been called.
+ * @param plain_send_next set to true if plain send() or sendmsg() will be
+ *                        called next,
+ *                        set to false if TLS socket send(), sendfile() or
+ *                        writev() will be called next.
  * @param push_data whether to push data to the network from buffers
  */
 static void
 post_send_setopt (struct MHD_Connection *connection,
-                  bool plain_send,
+                  bool plain_send_next,
                   bool push_data)
 {
   int ret;
@@ -223,70 +500,163 @@ post_send_setopt (struct MHD_Connection *connection,
    * Final piece is indicated by push_data == true. */
   const bool buffer_data = (! push_data);
 
+  if (buffer_data)
+    return; /* Nothing to do after send(). */
+
+#ifndef MHD_USE_MSG_MORE
+  (void) plain_send_next; /* Mute compiler warning */
+#endif /* ! MHD_USE_MSG_MORE */
+
+  /* Need to push data. */
+#ifdef MHD_TCP_CORK_NOPUSH
+#ifdef _MHD_CORK_RESET_PUSH_DATA_ALWAYS
+#ifdef _MHD_NODELAY_SET_PUSH_DATA_ALWAYS
 #ifdef MHD_USE_MSG_MORE
-  if (plain_send)
+  if (_MHD_OFF == connection->sk_corked)
   {
-    /* MSG_MORE is used, no need for extra syscalls! */
-    return;
+    if (_MHD_ON == connection->sk_nodelay)
+      return; /* Data was already pushed by send(). */
+  }
+  /* This is Linux kernel. There are options:
+   * * Push the data by setting of TCP_NODELAY (without change
+   *   of the cork on the socket),
+   * * Push the data by resetting of TCP_CORK.
+   * The optimal choice depends on the next final send functions
+   * used on the same socket. If TCP_NODELAY wasn't set then push
+   * data by setting TCP_NODELAY (TCP_NODELAY will not be removed
+   * and is needed to push the data by send() without MSG_MORE).
+   * If send()/sendmsg() will be used next than push data by
+   * reseting of TCP_CORK so next send without MSG_MORE will push
+   * data to the network (without additional sys-call to push data).
+   * If next final send function will not support MSG_MORE (like
+   * sendfile() or TLS-connection) than push data by setting
+   * TCP_NODELAY so socket will remain corked (no additional
+   * sys-call before next send()). */
+  if ((_MHD_ON != connection->sk_nodelay) ||
+      (! plain_send_next))
+  {
+    if (connection_set_nodelay_state_ (connection, true))
+      return; /* Data has been pushed by TCP_NODELAY. */
+    /* Failed to set TCP_NODELAY for the socket.
+     * Really unlikely to happen on TCP connections. */
+    if (connection_set_cork_state_ (connection, false))
+      return; /* Data has been pushed by uncorking the socket. */
+    /* Failed to uncork the socket.
+     * Really unlikely to happen on TCP connections. */
+
+    /* The socket cannot be uncorked, no way to push data */
+  }
+  else
+  {
+    if (connection_set_cork_state_ (connection, false))
+      return; /* Data has been pushed by uncorking the socket. */
+    /* Failed to uncork the socket.
+     * Really unlikely to happen on TCP connections. */
+    if (connection_set_nodelay_state_ (connection, true))
+      return; /* Data has been pushed by TCP_NODELAY. */
+    /* Failed to set TCP_NODELAY for the socket.
+     * Really unlikely to happen on TCP connections. */
+
+    /* The socket cannot be uncorked, no way to push data */
   }
 #else  /* ! MHD_USE_MSG_MORE */
-  (void) plain_send; /* Mute compiler warning. */
+  /* Use setting of TCP_NODELAY here to avoid sys-call
+   * for corking the socket during sending of the next response. */
+  if (connection_set_nodelay_state_ (connection, true))
+    return; /* Data was pushed by TCP_NODELAY. */
+  /* Failed to set TCP_NODELAY for the socket.
+   * Really unlikely to happen on TCP connections. */
+  if (connection_set_cork_state_ (connection, false))
+    return; /* Data was pushed by uncorking the socket. */
+  /* Failed to uncork the socket.
+   * Really unlikely to happen on TCP connections. */
+
+  /* The socket remains corked, no way to push data */
 #endif /* ! MHD_USE_MSG_MORE */
+#else  /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
+  if (connection_set_cork_state_ (connection, false))
+    return; /* Data was pushed by uncorking the socket. */
+  /* Failed to uncork the socket.
+   * Really unlikely to happen on TCP connections. */
+  return; /* Socket remains corked, no way to push data */
+#endif /* ! _MHD_NODELAY_SET_PUSH_DATA_ALWAYS */
+#else  /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
+  /* This is a typical *BSD or Darwin kernel. */
 
-#if defined(MHD_TCP_CORK_NOPUSH)
-  /* If connection is already in required corked state, do nothing. */
-  if (connection->sk_corked == buffer_data)
-    return;
-  if (buffer_data)
-    return; /* nothing to do *post* syscall (in fact, we should never
-               get here, as sk_corked should have succeeded in the
-               pre-syscall) */
-  ret = MHD_socket_cork_ (connection->socket_fd,
-                          buffer_data);
-  if (0 != ret)
+  if (_MHD_OFF == connection->sk_corked)
   {
-    connection->sk_corked = buffer_data;
-    return;
+    if (_MHD_ON == connection->sk_nodelay)
+      return; /* Data was already pushed by send(). */
+
+    /* Unlikely to reach this code.
+     * TCP_NODELAY should be turned on before send(). */
+    if (connection_set_nodelay_state_ (connection, true))
+    {
+      /* TCP_NODELAY has been set on uncorked socket.
+       * Use zero-send to push the data. */
+      if (zero_send_ (connection))
+        return; /* The data has been pushed by zero-send. */
+    }
+
+    /* Failed to push the data by all means. */
+    /* There is nothing left to try. */
   }
-  switch (errno)
+  else
   {
-  case ENOTSOCK:
-    /* FIXME: Could be we are talking to a pipe, maybe remember this
-       and avoid all setsockopt() in the future? */
-    break;
-  case EBADF:
-    /* FIXME: should we die hard here? */
-    break;
-  case EINVAL:
-    /* FIXME: optlen invalid, should at least log this, maybe die */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ ("optlen invalid: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  case EFAULT:
-    /* wopsie, should at least log this, FIXME: maybe die */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ (
-                "The address pointed to by optval is not a valid part of the 
process address space: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  case ENOPROTOOPT:
-    /* optlen unknown, should at least log this */
-#ifdef HAVE_MESSAGES
-    MHD_DLOG (connection->daemon,
-              _ ("The option is unknown: %s\n"),
-              MHD_socket_last_strerr_ ());
-#endif
-    break;
-  default:
-    /* any others? man page does not list more... */
-    break;
+#ifdef _MHD_CORK_RESET_PUSH_DATA
+    enum MHD_tristate old_cork_state = connection->sk_corked;
+#endif /* _MHD_CORK_RESET_PUSH_DATA */
+    /* The socket is corked or cork state is unknown. */
+
+    if (connection_set_cork_state_ (connection, false))
+    {
+#ifdef _MHD_CORK_RESET_PUSH_DATA
+      /* FreeBSD kernel */
+      if (_MHD_OFF == old_cork_state)
+        return; /* Data has been pushed by uncorking the socket. */
+#endif /* _MHD_CORK_RESET_PUSH_DATA */
+
+      /* Unlikely to reach this code.
+       * The data should be pushed by uncorking (FreeBSD) or
+       * the socket should be uncorked before send(). */
+      if ((_MHD_ON == connection->sk_nodelay) ||
+          (connection_set_nodelay_state_ (connection, true)))
+      {
+        /* TCP_NODELAY is turned ON on uncorked socket.
+         * Use zero-send to push the data. */
+        if (zero_send_ (connection))
+          return; /* The data has been pushed by zero-send. */
+      }
+    }
+    /* The socket remains corked. Data cannot be pushed. */
   }
-#endif
+#endif /* ! _MHD_CORK_RESET_PUSH_DATA_ALWAYS */
+#else  /* ! MHD_TCP_CORK_NOPUSH */
+  /* Corking is not supported. Buffering is controlled
+   * by TCP_NODELAY only. */
+  mhd_assert (_MHD_ON != connection->sk_corked);
+  if (_MHD_ON == connection->sk_nodelay)
+    return; /* Data was already pushed by send(). */
+
+  /* Unlikely to reach this code.
+   * TCP_NODELAY should be turned on before send(). */
+  if (connection_set_nodelay_state_ (connection, true))
+  {
+    /* TCP_NODELAY has been set.
+     * Use zero-send to push the data. */
+    if (zero_send_ (connection))
+      return; /* The data has been pushed by zero-send. */
+  }
+
+  /* Failed to push the data. */
+#endif /* ! MHD_TCP_CORK_NOPUSH */
+#ifdef HAVE_MESSAGES
+  MHD_DLOG (connection->daemon,
+            _ ("Failed to put the data from buffers to the network. "
+               "Client may experience some delay "
+               "(usually in range 200ms - 5 sec).\n"));
+#endif /* HAVE_MESSAGES */
+  return;
 }
 
 
@@ -422,8 +792,16 @@ MHD_send_on_connection_ (struct MHD_Connection *connection,
       connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
 #endif /* EPOLL_SUPPORT */
   }
-  post_send_setopt (connection, tls_conn,
-                    (push_data && (buffer_size == (size_t) ret)) );
+
+  /* If there is a need to push the data from network buffers
+   * call post_send_setopt(). */
+  /* If TLS connection is used then next final send() will be
+   * without MSG_MORE support. If non-TLS connection is used
+   * it's unknown whether sendfile() will be used or not so
+   * assume that next call will be the same, like this call. */
+  if ( (push_data) &&
+       (buffer_size == (size_t) ret) )
+    post_send_setopt (connection, (! tls_conn), push_data);
 
   return ret;
 }
@@ -455,7 +833,9 @@ MHD_send_on_connection2_ (struct MHD_Connection *connection,
 {
   MHD_socket s = connection->socket_fd;
   ssize_t ret;
+#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
   struct iovec vector[2];
+#endif /* HAVE_SENDMSG || HAVE_WRITEV */
 #ifdef HTTPS_SUPPORT
   const bool tls_conn = (connection->daemon->options & MHD_USE_TLS);
 #else  /* ! HTTPS_SUPPORT */
@@ -477,7 +857,13 @@ MHD_send_on_connection2_ (struct MHD_Connection 
*connection,
 #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
   /* Since we generally give the fully answer, we do not want
      corking to happen */
-  pre_send_setopt (connection, (! tls_conn), true);
+  pre_send_setopt (connection,
+#if HAVE_SENDMSG
+                   true,
+#elif HAVE_WRITEV
+                   false,
+#endif /* HAVE_WRITEV */
+                   true);
 
   vector[0].iov_base = (void *) header;
   vector[0].iov_len = header_size;
@@ -509,10 +895,21 @@ MHD_send_on_connection2_ (struct MHD_Connection 
*connection,
   }
 #endif
 
-  /* Only if we succeeded sending the full buffer, we need to make sure that
-     the OS flushes at the end */
-  post_send_setopt (connection, (! tls_conn),
-                    (header_size + buffer_size == (size_t) ret));
+  /* If there is a need to push the data from network buffers
+   * call post_send_setopt(). */
+  /* If TLS connection is used then next final send() will be
+   * without MSG_MORE support. If non-TLS connection is used
+   * it's unknown whether sendfile() will be used or not so
+   * assume that next final send() will be the same, like for
+   * this response. */
+  if ((header_size + buffer_size) == (size_t) ret)
+    post_send_setopt (connection,
+#if HAVE_SENDMSG
+                      true,
+#elif HAVE_WRITEV
+                      false,
+#endif /* HAVE_WRITEV */
+                      true);
 
   return ret;
 
@@ -563,22 +960,36 @@ MHD_send_sendfile_ (struct MHD_Connection *connection)
   const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ :
                             MHD_SENFILE_CHUNK_;
   size_t send_size = 0;
+  bool push_data;
   mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender);
   mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS));
 
-  pre_send_setopt (connection, false, true);
-
   offsetu64 = connection->response_write_position
               + connection->response->fd_off;
-  left = connection->response->total_size - 
connection->response_write_position;
-  /* Do not allow system to stick sending on single fast connection:
-   * use 128KiB chunks (2MiB for thread-per-connection). */
-  send_size = (left > chunk_size) ? chunk_size : (size_t) left;
   if (max_off_t < offsetu64)
   {   /* Retry to send with standard 'send()'. */
     connection->resp_sender = MHD_resp_sender_std;
     return MHD_ERR_AGAIN_;
   }
+
+  left = connection->response->total_size - 
connection->response_write_position;
+
+  if ( (uint64_t) SSIZE_MAX > left)
+    left = SSIZE_MAX;
+  /* Do not allow system to stick sending on single fast connection:
+   * use 128KiB chunks (2MiB for thread-per-connection). */
+  if (chunk_size < left)
+  {
+    send_size = chunk_size;
+    push_data = false; /* No need to push data, there is more to send, */
+  }
+  else
+  {
+    send_size = (size_t) left;
+    push_data = true; /* Final piece of data, need to push to the network. */
+  }
+  pre_send_setopt (connection, false, push_data);
+
 #ifdef MHD_LINUX_SOLARIS_SENDFILE
 #ifndef HAVE_SENDFILE64
   offset = (off_t) offsetu64;
@@ -708,9 +1119,13 @@ MHD_send_sendfile_ (struct MHD_Connection *connection)
   ret = (ssize_t) len;
 #endif /* HAVE_FREEBSD_SENDFILE */
 
-  /* Make sure we send the data without delay ONLY if we
-     provided the complete response (not on partial write) */
-  post_send_setopt (connection, false, (left == (uint64_t) ret));
+  /* If there is a need to push the data from network buffers
+   * call post_send_setopt(). */
+  /* It's unknown whether sendfile() will be used in the next
+   * response so  assume that next response will be the same. */
+  if ( (push_data) &&
+       (send_size == (size_t) ret) )
+    post_send_setopt (connection, false, push_data);
 
   return ret;
 }
diff --git a/src/microhttpd/mhd_send.h b/src/microhttpd/mhd_send.h
index 638dbade..046e7936 100644
--- a/src/microhttpd/mhd_send.h
+++ b/src/microhttpd/mhd_send.h
@@ -1,6 +1,7 @@
 /*
   This file is part of libmicrohttpd
   Copyright (C) 2019 ng0
+  Copyright (C) 2017 Karlson2k (Evgeny Grin)
 
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
@@ -20,8 +21,9 @@
 
 /**
  * @file mhd_send.h
- * @brief Implementation of send() wrappers.
+ * @brief Declarations of send() wrappers.
  * @author ng0
+ * @author Karlson2k (Evgeny Grin)
  */
 
 #ifndef MHD_SEND_H

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]