hypertable的application queue,非常好

/* -*- c++ -*-
 * Copyright (C) 2007-2013 Hypertable, Inc.
 *
 * This file is part of Hypertable.
 *
 * Hypertable is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or any later version.
 *
 * Hypertable 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/** @file
 * Declarations for ApplicationQueue.
 * This file contains type declarations for ApplcationQueue, a base class for
 * an application queue.
 */

#ifndef HYPERTABLE_APPLICATIONQUEUE_H
#define HYPERTABLE_APPLICATIONQUEUE_H

#include <cassert>
#include <list>
#include <map>
#include <vector>

#include <boost/thread/condition.hpp>
#include <boost/thread/xtime.hpp>

#include "Common/Thread.h"
#include "Common/Mutex.h"
#include "Common/HashMap.h"
#include "Common/ReferenceCount.h"
#include "Common/StringExt.h"
#include "Common/Logger.h"

#include "ApplicationQueueInterface.h"
#include "ApplicationHandler.h"

namespace Hypertable {

  /** @addtogroup AsyncComm
   *  @{
   */

  /**
   * Application queue.  
   * Helper class for use by server applications that are driven by messages
   * received over the network.  This class can be used in conjunction with the
   * ApplicationHandler class to implement an incoming request queue.  Worker
   * threads pull handlers (requests) off the queue and carry them out.  The
   * following features are supported:
   *
   * <b>Groups</b>
   *
   * Because a set of worker threads pull requests from the queue and carry
   * them out independently, it is possible for requests to get executed out
   * of order relative to the order in which they arrived in the queue.  This
   * can cause problems for certain request sequences such as appending data
   * to a file in the DfsBroker, or fetching scanner results from a scanner
   * using multiple readahead requests.  <i>Groups</i> are a way to give
   * applications the ability to serialize a set of requests.  Each request
   * has a <i>group ID</i> that is returned by 
   * the ApplicationHandler#get_group_id method.  Requests that have the
   * same group ID will get executed in series, in the order in which they
   * arrived in the application queue.  Requests with group ID 0 don't belong to
   * any group and will get executed independently with no serialization
   * order.
   *
   * <b>Prioritization</b>
   *
   * The ApplicationQueue supports two-level request prioritization.  Requests
   * can be designated as <i>urgent</i> which will cause them to be executed
   * before other non-urgent requests.  Urgent requests will also be executed
   * even when the ApplicationQueue has been paused.  In Hypertable, METADATA
   * scans and updates are marked urgent which allows them procede and prevent
   * deadlocks when the application queue gets paused due to low memory
   * condition in the RangeServer.  The ApplicationHandler#is_urgent
   * method is used to signal if a request is urgent.
   */
  class ApplicationQueue : public ApplicationQueueInterface {

    /** Tracks group execution state.
     * A GroupState object is created for each unique group ID to track the
     * queue execution state of requests in the group.
     */
    class GroupState {
    public:
      GroupState() : group_id(0), running(false), outstanding(1) { return; }
      uint64_t group_id;    //!< Group ID
      /** <i>true</i> if a request from this group is being executed */
      bool     running;
      /** Number of outstanding (uncompleted) requests in queue for this group*/
      int      outstanding;
    };

    /** Hash map of thread group ID to GroupState
     */ 
    typedef hash_map<uint64_t, GroupState *> GroupStateMap;

    /** Request record.
     */
    class RequestRec {
    public:
      RequestRec(ApplicationHandler *arh) : handler(arh), group_state(0) { return; }
      ~RequestRec() { delete handler; }
      ApplicationHandler *handler; //!< Pointer to ApplicationHandler
      GroupState *group_state;     //!< Pointer to GroupState to which request belongs
    };

    /** Individual request queue
     */
    typedef std::list<RequestRec *> RequestQueue;

    /** Application queue state shared among worker threads.
     */
    class ApplicationQueueState {
    public:
      ApplicationQueueState() : threads_available(0), shutdown(false),
                                paused(false) { }

      /// Normal request queue
      RequestQueue queue;

      /// Urgent request queue
      RequestQueue urgent_queue;

      /// Group ID to group state map
      GroupStateMap group_state_map;

      /// %Mutex for serializing concurrent access
      Mutex mutex;

      /// Condition variable to signal pending handlers
      boost::condition cond;

      /// Condition variable used to signal <i>quiesced</i> queue
      boost::condition quiesce_cond;

      /// Idle thread count
      size_t threads_available;
      
      /// Total initial threads
      size_t threads_total;

      /// Flag indicating if shutdown is in progress
      bool shutdown;

      /// Flag indicating if queue has been paused
      bool paused;
    };

    /** Application queue worker thread function (functor)
     */
    class Worker {

    public:
      Worker(ApplicationQueueState &qstate, bool one_shot=false) 
      : m_state(qstate), m_one_shot(one_shot) { return; }

      /** Thread run method
       */
      void operator()() {
        RequestRec *rec = 0;
        RequestQueue::iterator iter;

        while (true) {
          {
            ScopedLock lock(m_state.mutex);

            m_state.threads_available++;
            while ((m_state.paused || m_state.queue.empty()) &&
                   m_state.urgent_queue.empty()) {
              if (m_state.shutdown) {
                m_state.threads_available--;
                return;
              }
              if (m_state.threads_available == m_state.threads_total)
                m_state.quiesce_cond.notify_all();
              m_state.cond.wait(lock);
            }

            if (m_state.shutdown) {
              m_state.threads_available--;
              return;
            }

            rec = 0;

            iter = m_state.urgent_queue.begin();
            while (iter != m_state.urgent_queue.end()) {
              rec = (*iter);
              if (rec->group_state == 0 || !rec->group_state->running) {
                if (rec->group_state)
                  rec->group_state->running = true;
                m_state.urgent_queue.erase(iter);
                break;
              }
              if (!rec->handler || rec->handler->is_expired()) {
                iter = m_state.urgent_queue.erase(iter);
                remove_expired(rec);
              }
              rec = 0;
              iter++;
            }

            if (rec == 0 && !m_state.paused) {
              iter = m_state.queue.begin();
              while (iter != m_state.queue.end()) {
                rec = (*iter);
                if (rec->group_state == 0 || !rec->group_state->running) {
                  if (rec->group_state)
                    rec->group_state->running = true;
                  m_state.queue.erase(iter);
                  break;
                }
                if (!rec->handler || rec->handler->is_expired()) {
                  iter = m_state.queue.erase(iter);
                  remove_expired(rec);
                }
                rec = 0;
                iter++;
              }
            }

            if (rec == 0 && !m_one_shot) {
              if (m_state.shutdown) {
                m_state.threads_available--;
                return;
              }
              m_state.cond.wait(lock);
              if (m_state.shutdown) {
                m_state.threads_available--;
                return;
              }
            }

            m_state.threads_available--;
          }

          if (rec) {
            if (rec->handler)
              rec->handler->run();
            remove(rec);
            if (m_one_shot)
              return;
          }
          else if (m_one_shot)
            return;
        }

        HT_INFO("thread exit");
      }

    private:

      /** Removes and deletes a request.  This method updates the group
       * state associated with <code>rec</code> by setting the running flag to
       * <i>false</i> and decrementing the outstanding count.  If the
       * outstanding count for the group drops to 0, the group state record
       * is removed from ApplicationQueueState::group_state_map and is deleted.
       * @param rec Request record to remove
       */
      void remove(RequestRec *rec) {
        if (rec->group_state) {
          ScopedLock ulock(m_state.mutex);
          rec->group_state->running = false;
          rec->group_state->outstanding--;
          if (rec->group_state->outstanding == 0) {
            m_state.group_state_map.erase(rec->group_state->group_id);
            delete rec->group_state;
          }
        }
        delete rec;
      }

      /** Removes and deletes an expired request.  This method updates the group
       * state associated with <code>rec</code> by decrementing the outstanding
       * count.  If the outstanding count for the group drops to 0, the group
       * state record is removed from ApplicationQueueState::group_state_map and
       * is deleted.
       * @param rec Request record to remove
       */
      void remove_expired(RequestRec *rec) {
        if (rec->group_state) {
          rec->group_state->outstanding--;
          if (rec->group_state->outstanding == 0) {
            m_state.group_state_map.erase(rec->group_state->group_id);
            delete rec->group_state;
          }
        }
        delete rec;
      }

      /// Shared application queue state object
      ApplicationQueueState &m_state;

      /// Set to <i>true</i> if thread should exit after executing request
      bool m_one_shot;
    };

    /// Application queue state object
    ApplicationQueueState m_state;
    
    /// Boost thread group for managing threads
    ThreadGroup m_threads;

    /// Vector of thread IDs
    std::vector<Thread::id> m_thread_ids;

    /// Flag indicating if threads have joined after a shutdown
    bool joined;

    /** Set to <i>true</i> if queue is configured to allow dynamic thread
     * creation
     */
    bool m_dynamic_threads;

  public:

    /** Default constructor used by derived classes only
     */
    ApplicationQueue() : joined(true) {}

    /**
     * Constructor initialized with worker thread count.
     * This constructor sets up the application queue with a number of worker
     * threads specified by <code>worker_count</code>.
     * @param worker_count Number of worker threads to create
     * @param dynamic_threads Dynamically create temporary thread to carry out
     * requests if none available.
     */
    ApplicationQueue(int worker_count, bool dynamic_threads=true) 
      : joined(false), m_dynamic_threads(dynamic_threads) {
      m_state.threads_total = worker_count;
      Worker Worker(m_state);
      assert (worker_count > 0);
      for (int i=0; i<worker_count; ++i) {
        m_thread_ids.push_back(m_threads.create_thread(Worker)->get_id());
      }
      //threads
    }

    /** Destructor.
     */
    virtual ~ApplicationQueue() {
      if (!joined) {
        shutdown();
        join();
      }
    }

    /**
     * Returns all the thread IDs for this threadgroup
     * @return vector of Thread::id
     */
    std::vector<Thread::id> get_thread_ids() const {
      return m_thread_ids;
    }

    /**
     * Shuts down the application queue.  All outstanding requests are carried
     * out and then all threads exit.  #join can be called to wait for
     * completion of the shutdown.
     */
    void shutdown() {
      ScopedLock lock(m_state.mutex);
      m_state.shutdown = true;
      m_state.cond.notify_all();
    }

    /** Wait for queue to become idle (with timeout).
     * @param deadline Return by this time if queue does not become idle
     * @param reserve_threads Number of threads that can be active when queue is
     * idle
     * @return <i>false</i> if <code>deadline</code> was reached before queue
     * became idle, <i>true</i> otherwise
     */
    bool wait_for_idle(boost::xtime &deadline, int reserve_threads=0) {
      ScopedLock lock(m_state.mutex);
      while (m_state.threads_available < (m_state.threads_total-reserve_threads)) {
        if (!m_state.quiesce_cond.timed_wait(lock, deadline))
          return false;
      }
      return true;
    }

    /**
     * Waits for a shutdown to complete.  This method returns when all
     * application queue threads exit.
     */
    void join() {
      if (!joined) {
        m_threads.join_all();
        joined = true;
      }
    }

    /** Starts application queue.
     */
    void start() {
      ScopedLock lock(m_state.mutex);
      m_state.paused = false;
      m_state.cond.notify_all();
    }

    /** Stops (pauses) application queue, preventing non-urgent requests from
     * being executed.  Any requests that are being executed at the time of the
     * call are allowed to complete.
     */
    void stop() {
      ScopedLock lock(m_state.mutex);
      m_state.paused = true;
    }

    /** Adds a request (application request handler) to the application queue.
     * The request queue is designed to support the serialization of related
     * requests.  Requests are related by the thread group ID value in the
     * ApplicationHandler.  This thread group ID is constructed in the
     * Event object.
     * @param app_handler Pointer to request to add
     */
    virtual void add(ApplicationHandler *app_handler) {
      GroupStateMap::iterator uiter;
      uint64_t group_id = app_handler->get_group_id();
      RequestRec *rec = new RequestRec(app_handler);
      rec->group_state = 0;

      HT_ASSERT(app_handler);

      ScopedLock lock(m_state.mutex);

      if (group_id != 0) {
        if ((uiter = m_state.group_state_map.find(group_id))
            != m_state.group_state_map.end()) {
          rec->group_state = (*uiter).second;
          rec->group_state->outstanding++;
        }
        else {
          rec->group_state = new GroupState();
          rec->group_state->group_id = group_id;
          m_state.group_state_map[group_id] = rec->group_state;
        }
      }

      if (app_handler->is_urgent()) {
        m_state.urgent_queue.push_back(rec);
        if (m_dynamic_threads && m_state.threads_available == 0) {
          Worker worker(m_state, true);
          Thread t(worker);
          return;
        }
      }
      else
        m_state.queue.push_back(rec);
      m_state.cond.notify_one();
    }

    /** Adds a request (application request handler) to the application queue.
     * The request queue is designed to support the serialization of related
     * requests.  Requests are related by the thread group ID value in the
     * ApplicationHandler.  This thread group ID is constructed in the
     * Event object.
     * @note This method is defined for symmetry and just calls #add
     * @param app_handler Pointer to request to add
     */
    virtual void add_unlocked(ApplicationHandler *app_handler) {
      add(app_handler);
    }
  };

  /// Smart pointer to ApplicationQueue object
  typedef boost::intrusive_ptr<ApplicationQueue> ApplicationQueuePtr;
  /** @}*/
} // namespace Hypertable

#endif // HYPERTABLE_APPLICATIONQUEUE_H

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值