/*
  This file is part of libmicrohttpd
  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file lib/request_resume.c
 * @brief implementation of MHD_request_resume()
 * @author Christian Grothoff
 */
#include "internal.h"
#include "connection_close.h"

/**
 * Resume handling of network data for suspended request.  It is
 * safe to resume a suspended request at any time.  Calling this
 * function on a request that was not previously suspended will
 * result in undefined behavior.
 *
 * If you are using this function in ``external'' select mode, you must
 * make sure to run #MHD_run() afterwards (before again calling
 * #MHD_get_fdset(), as otherwise the change may not be reflected in
 * the set returned by #MHD_get_fdset() and you may end up with a
 * request that is stuck until the next network activity.
 *
 * @param request the request to resume
 */
void
MHD_request_resume (struct MHD_Request *request)
{
  struct MHD_Daemon *daemon = request->daemon;

  if (daemon->disallow_suspend_resume)
    MHD_PANIC (_ (
                 "Cannot resume connections without enabling MHD_ALLOW_SUSPEND_RESUME!\n"));
  MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
  request->connection->resuming = true;
  daemon->resuming = true;
  MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
  if ( (MHD_ITC_IS_VALID_ (daemon->itc)) &&
       (! MHD_itc_activate_ (daemon->itc,
                             "r")) )
  {
#ifdef HAVE_MESSAGES
    MHD_DLOG (daemon,
              MHD_SC_ITC_USE_FAILED,
              _ (
                "Failed to signal resume via inter-thread communication channel.\n"));
#endif
  }
}


/**
 * Run through the suspended connections and move any that are no
 * longer suspended back to the active state.
 *
 * @remark To be called only from thread that process
 * daemon's select()/poll()/etc.
 *
 * @param daemon daemon context
 * @return true if a connection was actually resumed
 */
bool
MHD_resume_suspended_connections_ (struct MHD_Daemon *daemon)
/* FIXME: rename connections -> requests? */
{
  struct MHD_Connection *pos;
  struct MHD_Connection *prev = NULL;
  bool ret;
  const bool used_thr_p_c = (MHD_TM_THREAD_PER_CONNECTION ==
                             daemon->threading_mode);

  mhd_assert (NULL == daemon->worker_pool);
  ret = false;
  MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);

  if (daemon->resuming)
  {
    prev = daemon->suspended_connections_tail;
    /* During shutdown check for resuming is forced. */
    mhd_assert ((NULL != prev) || (daemon->shutdown));
  }

  daemon->resuming = false;

  while (NULL != (pos = prev))
  {
#ifdef UPGRADE_SUPPORT
    struct MHD_UpgradeResponseHandle *const urh = pos->request.urh;
#else  /* ! UPGRADE_SUPPORT */
    static const void *const urh = NULL;
#endif /* ! UPGRADE_SUPPORT */
    prev = pos->prev;
    if ( (! pos->resuming)
#ifdef UPGRADE_SUPPORT
         || ( (NULL != urh) &&
              ( (! urh->was_closed) ||
                (! urh->clean_ready) ) )
#endif /* UPGRADE_SUPPORT */
         )
      continue;
    ret = true;
    mhd_assert (pos->suspended);
    DLL_remove (daemon->suspended_connections_head,
                daemon->suspended_connections_tail,
                pos);
    pos->suspended = false;
    if (NULL == urh)
    {
      DLL_insert (daemon->connections_head,
                  daemon->connections_tail,
                  pos);
      if (! used_thr_p_c)
      {
        /* Reset timeout timer on resume. */
        if (0 != pos->connection_timeout)
          pos->last_activity = MHD_monotonic_sec_counter ();

        if (pos->connection_timeout == daemon->connection_default_timeout)
          XDLL_insert (daemon->normal_timeout_head,
                       daemon->normal_timeout_tail,
                       pos);
        else
          XDLL_insert (daemon->manual_timeout_head,
                       daemon->manual_timeout_tail,
                       pos);
      }
#ifdef EPOLL_SUPPORT
      if (MHD_ELS_EPOLL == daemon->event_loop_syscall)
      {
        if (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL))
          MHD_PANIC ("Resumed connection was already in EREADY set.\n");
        /* we always mark resumed connections as ready, as we
           might have missed the edge poll event during suspension */
        EDLL_insert (daemon->eready_head,
                     daemon->eready_tail,
                     pos);
        pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL   \
                            | MHD_EPOLL_STATE_READ_READY
                            | MHD_EPOLL_STATE_WRITE_READY;
        pos->epoll_state &= ~MHD_EPOLL_STATE_SUSPENDED;
      }
#endif
    }
#ifdef UPGRADE_SUPPORT
    else
    {
      struct MHD_Response *response = pos->request.response;

      /* Data forwarding was finished (for TLS connections) AND
       * application was closed upgraded connection.
       * Insert connection into cleanup list. */
      if ( (NULL != response) &&
           (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) &&
           (NULL != response->termination_cb) )
        response->termination_cb (response->termination_cb_cls,
                                  MHD_REQUEST_TERMINATED_COMPLETED_OK,
                                  &pos->request.client_context);
      DLL_insert (daemon->cleanup_head,
                  daemon->cleanup_tail,
                  pos);

    }
#endif /* UPGRADE_SUPPORT */
    pos->resuming = false;
  }
  MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
  if ( (used_thr_p_c) &&
       (ret) )
  {   /* Wake up suspended connections. */
    if (! MHD_itc_activate_ (daemon->itc,
                             "w"))
    {
#ifdef HAVE_MESSAGES
      MHD_DLOG (daemon,
                MHD_SC_ITC_USE_FAILED,
                _ (
                  "Failed to signal resume of connection via inter-thread communication channel.\n"));
#endif
    }
  }
  return ret;
}


/* end of request_resume.c */
