httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From j..@apache.org
Subject svn commit: r1590597 [9/14] - in /httpd/httpd/trunk/modules/spdy: ./ apache/ apache/filters/ apache/testing/ common/ common/testing/ support/ support/base/ support/base/metrics/ support/build/ support/install/ support/install/common/ support/install/de...
Date Mon, 28 Apr 2014 10:55:22 GMT
Added: httpd/httpd/trunk/modules/spdy/common/thread_pool.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/thread_pool.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/thread_pool.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/thread_pool.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,490 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/thread_pool.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/executor.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace {
+
+// Shut down a worker thread after it has been idle for this many seconds:
+const int64 kDefaultMaxWorkerIdleSeconds = 60;
+
+}  // namespace
+
+namespace mod_spdy {
+
+// An executor that uses the ThreadPool to execute tasks.  Returned by
+// ThreadPool::NewExecutor.
+class ThreadPool::ThreadPoolExecutor : public Executor {
+ public:
+  explicit ThreadPoolExecutor(ThreadPool* master)
+      : master_(master),
+        stopping_condvar_(&master_->lock_),
+        stopped_(false) {}
+  virtual ~ThreadPoolExecutor() { Stop(); }
+
+  // Executor methods:
+  virtual void AddTask(net_instaweb::Function* task,
+                       net::SpdyPriority priority);
+  virtual void Stop();
+
+ private:
+  friend class ThreadPool;
+  ThreadPool* const master_;
+  base::ConditionVariable stopping_condvar_;
+  bool stopped_;  // protected by master_->lock_
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadPoolExecutor);
+};
+
+// Add a task to the executor; if the executor has already been stopped, just
+// cancel the task immediately.
+void ThreadPool::ThreadPoolExecutor::AddTask(net_instaweb::Function* task,
+                                             net::SpdyPriority priority) {
+  {
+    base::AutoLock autolock(master_->lock_);
+
+    // Clean up any zombie WorkerThreads in the ThreadPool that are waiting for
+    // reaping.  If the OS process we're in accumulates too many unjoined
+    // zombie threads over time, the OS might not be able to spawn a new thread
+    // below.  So right now is a good time to clean them up.
+    if (!master_->zombies_.empty()) {
+      std::set<WorkerThread*> zombies;
+      zombies.swap(master_->zombies_);
+      // Joining these threads should be basically instant, since they've
+      // already terminated.  But to be safe, let's unlock while we join them.
+      base::AutoUnlock autounlock(master_->lock_);
+      ThreadPool::JoinThreads(zombies);
+    }
+
+    // The thread pool shouldn't be shutting down until all executors are
+    // destroyed.  Since this executor clearly still exists, the thread pool
+    // must still be open.
+    DCHECK(!master_->shutting_down_);
+
+    // If the executor hasn't been stopped, add the task to the queue and
+    // notify a worker that there's a new task ready to be taken.
+    if (!stopped_) {
+      master_->task_queue_.insert(std::make_pair(priority, Task(task, this)));
+      master_->worker_condvar_.Signal();
+      master_->StartNewWorkerIfNeeded();
+      return;
+    }
+  }
+
+  // If this executor has already been stopped, just cancel the task (after
+  // releasing the lock).
+  task->CallCancel();
+}
+
+// Stop the executor.  Cancel all pending tasks in the thread pool owned by
+// this executor, and then block until all active tasks owned by this executor
+// complete.  Stopping the executor more than once has no effect.
+void ThreadPool::ThreadPoolExecutor::Stop() {
+  std::vector<net_instaweb::Function*> functions_to_cancel;
+  {
+    base::AutoLock autolock(master_->lock_);
+    if (stopped_) {
+      return;
+    }
+    stopped_ = true;
+
+    // Remove all tasks owned by this executor from the queue, and collect up
+    // the function objects to be cancelled.
+    TaskQueue::iterator next_iter = master_->task_queue_.begin();
+    while (next_iter != master_->task_queue_.end()) {
+      TaskQueue::iterator iter = next_iter;
+      const Task& task = iter->second;
+      ++next_iter;  // Increment next_iter _before_ we might erase iter.
+      if (task.owner == this) {
+        functions_to_cancel.push_back(task.function);
+        master_->task_queue_.erase(iter);
+      }
+    }
+  }
+
+  // Unlock while we cancel the functions, so we're not hogging the lock for
+  // too long, and to avoid potential deadlock if the cancel method tries to do
+  // anything with the thread pool.
+  for (std::vector<net_instaweb::Function*>::const_iterator iter =
+           functions_to_cancel.begin();
+       iter != functions_to_cancel.end(); ++iter) {
+    (*iter)->CallCancel();
+  }
+  // CallCancel deletes the Function objects, invalidating the pointers in this
+  // list, so let's go ahead and clear it (which also saves a little memory
+  // while we're blocked below).
+  functions_to_cancel.clear();
+
+  // Block until all our active tasks are completed.
+  {
+    base::AutoLock autolock(master_->lock_);
+    while (master_->active_task_counts_.count(this) > 0) {
+      stopping_condvar_.Wait();
+    }
+  }
+}
+
+// A WorkerThread object wraps a platform-specific thread handle, and provides
+// the method run by that thread (ThreadMain).
+class ThreadPool::WorkerThread : public base::PlatformThread::Delegate {
+ public:
+  explicit WorkerThread(ThreadPool* master);
+  virtual ~WorkerThread();
+
+  // Start the thread running.  Return false on failure.  If this succeeds,
+  // then you must call Join() before deleting this object.
+  bool Start();
+
+  // Block until the thread completes.  You must set master_->shutting_down_ to
+  // true before calling this method, or the thread will never terminate.
+  // You shouldn't be holding master_->lock_ when calling this.
+  void Join();
+
+  // base::PlatformThread::Delegate method:
+  virtual void ThreadMain();
+
+ private:
+  enum ThreadState { NOT_STARTED, STARTED, JOINED };
+
+  ThreadPool* const master_;
+  // If two master threads are sharing the same ThreadPool, then Start() and
+  // Join() might get called by different threads.  So to be safe we use a lock
+  // to protect the two below fields.
+  base::Lock thread_lock_;
+  ThreadState state_;
+  base::PlatformThreadHandle thread_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(WorkerThread);
+};
+
+ThreadPool::WorkerThread::WorkerThread(ThreadPool* master)
+    : master_(master), state_(NOT_STARTED), thread_id_() {}
+
+ThreadPool::WorkerThread::~WorkerThread() {
+  base::AutoLock autolock(thread_lock_);
+  // If we started the thread, we _must_ join it before deleting this object,
+  // or else the thread won't get cleaned up by the OS.
+  DCHECK(state_ == NOT_STARTED || state_ == JOINED);
+}
+
+bool ThreadPool::WorkerThread::Start() {
+  base::AutoLock autolock(thread_lock_);
+  DCHECK_EQ(NOT_STARTED, state_);
+  if (base::PlatformThread::Create(0, this, &thread_id_)) {
+    state_ = STARTED;
+    return true;
+  }
+  return false;
+}
+
+void ThreadPool::WorkerThread::Join() {
+  base::AutoLock autolock(thread_lock_);
+  DCHECK_EQ(STARTED, state_);
+  base::PlatformThread::Join(thread_id_);
+  state_ = JOINED;
+}
+
+// This is the code executed by the thread; when this method returns, the
+// thread will terminate.
+void ThreadPool::WorkerThread::ThreadMain() {
+  // We start by grabbing the master lock, but we release it below whenever we
+  // are 1) waiting for a new task or 2) executing a task.  So in fact most of
+  // the time we are not holding the lock.
+  base::AutoLock autolock(master_->lock_);
+  while (true) {
+    // Wait until there's a task available (or we're shutting down), but don't
+    // stay idle for more than kMaxWorkerIdleSeconds seconds.
+    base::TimeDelta time_remaining = master_->max_thread_idle_time_;
+    while (!master_->shutting_down_ && master_->task_queue_.empty() &&
+           time_remaining.InSecondsF() > 0.0) {
+      // Note that TimedWait can wake up spuriously before the time runs out,
+      // so we need to measure how long we actually waited for.
+      const base::Time start = base::Time::Now();
+      master_->worker_condvar_.TimedWait(time_remaining);
+      const base::Time end = base::Time::Now();
+      // Note that the system clock can go backwards if it is reset, so make
+      // sure we never _increase_ time_remaining.
+      if (end > start) {
+        time_remaining -= end - start;
+      }
+    }
+
+    // If the thread pool is shutting down, terminate this thread; the master
+    // is about to join/delete us (in its destructor).
+    if (master_->shutting_down_) {
+      return;
+    }
+
+    // If we ran out of time without getting a task, maybe this thread should
+    // shut itself down.
+    if (master_->task_queue_.empty()) {
+      DCHECK_LE(time_remaining.InSecondsF(), 0.0);
+      // Ask the master if we should stop.  If this returns true, this worker
+      // has been zombified, so we're free to terminate the thread.
+      if (master_->TryZombifyIdleThread(this)) {
+        return;  // Yes, we should stop; terminate the thread.
+      } else {
+        continue;  // No, we shouldn't stop; jump to the top of the while loop.
+      }
+    }
+
+    // Otherwise, there must be at least one task available now.  Grab one from
+    // the master, who will then treat us as busy until we complete it.
+    const Task task = master_->GetNextTask();
+    // Release the lock while we execute the task.  Note that we use AutoUnlock
+    // here rather than one AutoLock for the above code and another for the
+    // below code, so that we don't have to release and reacquire the lock at
+    // the edge of the while-loop.
+    {
+      base::AutoUnlock autounlock(master_->lock_);
+      task.function->CallRun();
+    }
+    // Inform the master we have completed the task and are no longer busy.
+    master_->OnTaskComplete(task);
+  }
+}
+
+ThreadPool::ThreadPool(int min_threads, int max_threads)
+    : min_threads_(min_threads),
+      max_threads_(max_threads),
+      max_thread_idle_time_(
+          base::TimeDelta::FromSeconds(kDefaultMaxWorkerIdleSeconds)),
+      worker_condvar_(&lock_),
+      num_busy_workers_(0),
+      shutting_down_(false) {
+  DCHECK_GE(max_thread_idle_time_.InSecondsF(), 0.0);
+  // Note that we check e.g. min_threads rather than min_threads_ (which is
+  // unsigned), in order to catch negative numbers.
+  DCHECK_GE(min_threads, 1);
+  DCHECK_GE(max_threads, 1);
+  DCHECK_LE(min_threads_, max_threads_);
+}
+
+ThreadPool::ThreadPool(int min_threads, int max_threads,
+                       base::TimeDelta max_thread_idle_time)
+    : min_threads_(min_threads),
+      max_threads_(max_threads),
+      max_thread_idle_time_(max_thread_idle_time),
+      worker_condvar_(&lock_),
+      num_busy_workers_(0),
+      shutting_down_(false) {
+  DCHECK_GE(max_thread_idle_time_.InSecondsF(), 0.0);
+  DCHECK_GE(min_threads, 1);
+  DCHECK_GE(max_threads, 1);
+  DCHECK_LE(min_threads_, max_threads_);
+}
+
+ThreadPool::~ThreadPool() {
+  base::AutoLock autolock(lock_);
+
+  // If we're doing things right, all the Executors should have been
+  // destroyed before the ThreadPool is destroyed, so there should be no
+  // pending or active tasks.
+  DCHECK(task_queue_.empty());
+  DCHECK(active_task_counts_.empty());
+
+  // Wake up all the worker threads and tell them to shut down.
+  shutting_down_ = true;
+  worker_condvar_.Broadcast();
+
+  // Clean up all our threads.
+  std::set<WorkerThread*> threads;
+  zombies_.swap(threads);
+  threads.insert(workers_.begin(), workers_.end());
+  workers_.clear();
+  {
+    base::AutoUnlock autounlock(lock_);
+    JoinThreads(threads);
+  }
+
+  // Because we had shutting_down_ set to true, nothing should have been added
+  // to our WorkerThread sets while we were unlocked.  So we should be all
+  // cleaned up now.
+  DCHECK(workers_.empty());
+  DCHECK(zombies_.empty());
+  DCHECK(task_queue_.empty());
+  DCHECK(active_task_counts_.empty());
+}
+
+bool ThreadPool::Start() {
+  base::AutoLock autolock(lock_);
+  DCHECK(task_queue_.empty());
+  DCHECK(workers_.empty());
+  // Start up min_threads_ workers; if any of the worker threads fail to start,
+  // then this method fails and the ThreadPool should be deleted.
+  for (unsigned int i = 0; i < min_threads_; ++i) {
+    scoped_ptr<WorkerThread> worker(new WorkerThread(this));
+    if (!worker->Start()) {
+      return false;
+    }
+    workers_.insert(worker.release());
+  }
+  DCHECK_EQ(min_threads_, workers_.size());
+  return true;
+}
+
+Executor* ThreadPool::NewExecutor() {
+  return new ThreadPoolExecutor(this);
+}
+
+int ThreadPool::GetNumWorkersForTest() {
+  base::AutoLock autolock(lock_);
+  return workers_.size();
+}
+
+int ThreadPool::GetNumIdleWorkersForTest() {
+  base::AutoLock autolock(lock_);
+  DCHECK_GE(num_busy_workers_, 0u);
+  DCHECK_LE(num_busy_workers_, workers_.size());
+  return workers_.size() - num_busy_workers_;
+}
+
+int ThreadPool::GetNumZombiesForTest() {
+  base::AutoLock autolock(lock_);
+  return zombies_.size();
+}
+
+// This method is called each time we add a new task to the thread pool.
+void ThreadPool::StartNewWorkerIfNeeded() {
+  lock_.AssertAcquired();
+  DCHECK_GE(num_busy_workers_, 0u);
+  DCHECK_LE(num_busy_workers_, workers_.size());
+  DCHECK_GE(workers_.size(), min_threads_);
+  DCHECK_LE(workers_.size(), max_threads_);
+
+  // We create a new worker to handle the task _unless_ either 1) we're already
+  // at the maximum number of threads, or 2) there are already enough idle
+  // workers sitting around to take on this task (and all other pending tasks
+  // that the idle workers haven't yet had a chance to pick up).
+  if (workers_.size() >= max_threads_ ||
+      task_queue_.size() <= workers_.size() - num_busy_workers_) {
+    return;
+  }
+
+  scoped_ptr<WorkerThread> worker(new WorkerThread(this));
+  if (worker->Start()) {
+    workers_.insert(worker.release());
+  } else {
+    LOG(ERROR) << "Failed to start new worker thread.";
+  }
+}
+
+// static
+void ThreadPool::JoinThreads(const std::set<WorkerThread*>& threads) {
+  for (std::set<WorkerThread*>::const_iterator iter = threads.begin();
+       iter != threads.end(); ++iter) {
+    WorkerThread* thread = *iter;
+    thread->Join();
+    delete thread;
+  }
+}
+
+// Call when the worker thread has been idle for a while.  Either return false
+// (worker should continue waiting for tasks), or zombify the worker and return
+// true (worker thread should immediately terminate).
+bool ThreadPool::TryZombifyIdleThread(WorkerThread* thread) {
+  lock_.AssertAcquired();
+
+  // Don't terminate the thread if the thread pool is already at the minimum
+  // number of threads.
+  DCHECK_GE(workers_.size(), min_threads_);
+  if (workers_.size() <= min_threads_) {
+    return false;
+  }
+
+  // Remove this thread from the worker set.
+  DCHECK_EQ(1u, workers_.count(thread));
+  workers_.erase(thread);
+
+  // When a (joinable) thread terminates, it must still be cleaned up, either
+  // by another thread joining it, or by detatching it.  However, the thread
+  // pool's not shutting down here, so the master thread doesn't know to join
+  // this thread that we're in now, and the Chromium thread abstraction we're
+  // using doesn't currently allow us to detach a thread.  So instead, we place
+  // this WorkerThread object into a "zombie" set, which the master thread can
+  // reap later on.  Threads that have terminated but that haven't been joined
+  // yet use up only a small amount of memory (I think), so it's okay if we
+  // don't reap it right away, as long as we don't try to spawn new threads
+  // while there's still lots of zombies.
+  DCHECK(!shutting_down_);
+  DCHECK_EQ(0u, zombies_.count(thread));
+  zombies_.insert(thread);
+  return true;
+}
+
+// Get and return the next task from the queue (which must be non-empty), and
+// update our various counters to indicate that the calling worker is busy
+// executing this task.
+ThreadPool::Task ThreadPool::GetNextTask() {
+  lock_.AssertAcquired();
+
+  // Pop the highest-priority task from the queue.  Note that smaller values
+  // correspond to higher priorities (SPDY draft 3 section 2.3.3), so
+  // task_queue_.begin() gets us the highest-priority pending task.
+  DCHECK(!task_queue_.empty());
+  TaskQueue::iterator task_iter = task_queue_.begin();
+  const Task task = task_iter->second;
+  task_queue_.erase(task_iter);
+
+  // Increment the count of active tasks for the executor that owns this
+  // task; we'll decrement it again when the task completes.
+  ++(active_task_counts_[task.owner]);
+
+  // The worker that takes this task will be busy until it completes it.
+  DCHECK_LT(num_busy_workers_, workers_.size());
+  ++num_busy_workers_;
+
+  return task;
+}
+
+// Call to indicate that the task has been completed; update our various
+// counters to indicate that the calling worker is no longer busy executing
+// this task.
+void ThreadPool::OnTaskComplete(Task task) {
+  lock_.AssertAcquired();
+
+  // The worker that just finished this task is no longer busy.
+  DCHECK_GE(num_busy_workers_, 1u);
+  --num_busy_workers_;
+
+  // We've completed the task and reaquired the lock, so decrement the count
+  // of active tasks for this owner.
+  OwnerMap::iterator count_iter = active_task_counts_.find(task.owner);
+  DCHECK(count_iter != active_task_counts_.end());
+  DCHECK(count_iter->second > 0);
+  --(count_iter->second);
+
+  // If this was the last active task for the owner, notify anyone who might be
+  // waiting for the owner to stop.
+  if (count_iter->second == 0) {
+    active_task_counts_.erase(count_iter);
+    task.owner->stopping_condvar_.Broadcast();
+  }
+}
+
+}  // namespace mod_spdy

Added: httpd/httpd/trunk/modules/spdy/common/thread_pool.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/thread_pool.h?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/thread_pool.h (added)
+++ httpd/httpd/trunk/modules/spdy/common/thread_pool.h Mon Apr 28 10:55:17 2014
@@ -0,0 +1,145 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MOD_SPDY_COMMON_THREAD_POOL_H_
+#define MOD_SPDY_COMMON_THREAD_POOL_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/spdy/spdy_protocol.h"  // for net::SpdyPriority
+
+namespace net_instaweb { class Function; }
+
+namespace mod_spdy {
+
+class Executor;
+
+// A ThreadPool keeps a pool of threads waiting to perform tasks.  One can
+// create any number of Executor objects, using the NewExecutor method, which
+// will all share the threads for executing tasks.  If more tasks are queued
+// than there are threads in the pool, these executors will respect task
+// priorities when deciding which tasks to execute first.
+class ThreadPool {
+ public:
+  // Create a new thread pool that uses at least min_threads threads, and at
+  // most max_threads threads, at a time.  min_threads must be no greater than
+  // max_threads, and both must be positive.
+  ThreadPool(int min_threads, int max_threads);
+
+  // As above, but specify the amount of time after which to kill idle threads,
+  // rather than using the default value (this is primarily for testing).
+  // max_thread_idle_time must be non-negative.
+  ThreadPool(int min_threads, int max_threads,
+             base::TimeDelta max_thread_idle_time);
+
+  // The destructor will block until all threads in the pool have shut down.
+  // The ThreadPool must not be destroyed until all Executor objects returned
+  // from the NewExecutor method have first been deleted.
+  ~ThreadPool();
+
+  // Start up the thread pool.  Must be called exactly one before using the
+  // thread pool; returns true on success, or false on failure.  If startup
+  // fails, the ThreadPool must be immediately deleted.
+  bool Start();
+
+  // Return a new Executor object that uses this thread pool to perform tasks.
+  // The caller gains ownership of the returned Executor, and the ThreadPool
+  // must outlive the returned Executor.
+  Executor* NewExecutor();
+
+  // Return the current total number of worker threads.  This is provided for
+  // testing purposes only.
+  int GetNumWorkersForTest();
+  // Return the number of worker threads currently idle.  This is provided for
+  // testing purposes only.
+  int GetNumIdleWorkersForTest();
+  // Return the number of terminated (zombie) threads that have yet to be
+  // reaped.  This is provided for testing purposes only.
+  int GetNumZombiesForTest();
+
+ private:
+  class ThreadPoolExecutor;
+  class WorkerThread;
+
+  // A Task is a simple pair of the Function to run, and the executor to which
+  // the task was added.
+  struct Task {
+    Task(net_instaweb::Function* fun, ThreadPoolExecutor* own)
+        : function(fun), owner(own) {}
+    net_instaweb::Function* function;
+    ThreadPoolExecutor* owner;
+  };
+
+  typedef std::multimap<net::SpdyPriority, Task> TaskQueue;
+  typedef std::map<const ThreadPoolExecutor*, int> OwnerMap;
+
+  // Start a new worker thread if 1) the task queue is larger than the number
+  // of currently idle workers, and 2) we have fewer than the maximum number of
+  // workers.  Otherwise, do nothing.  Must be holding lock_ when calling this.
+  void StartNewWorkerIfNeeded();
+
+  // Join and delete all worker threads in the given set.  This will block
+  // until all the threads have terminated and been cleaned up, so don't call
+  // this while holding the lock_.
+  static void JoinThreads(const std::set<WorkerThread*>& threads);
+
+  // These calls are used to implement the WorkerThread's main function.  Must
+  // be holding lock_ when calling any of these.
+  bool TryZombifyIdleThread(WorkerThread* thread);
+  Task GetNextTask();
+  void OnTaskComplete(Task task);
+
+  // The min and max number of threads passed to the constructor.  Although the
+  // constructor takes signed ints (for convenience), we store these unsigned
+  // to avoid the need for static_casts when comparing against workers_.size().
+  const unsigned int min_threads_;
+  const unsigned int max_threads_;
+  const base::TimeDelta max_thread_idle_time_;
+  // This single master lock protects all of the below fields, as well as any
+  // mutable data and condition variables in the worker threads and executors.
+  // Having just one lock makes everything much easier to understand.
+  base::Lock lock_;
+  // Workers wait on this condvar when waiting for a new task.  We signal it
+  // when a new task becomes available, or when we need to shut down.
+  base::ConditionVariable worker_condvar_;
+  // The list of running worker threads.  We keep this around so that we can
+  // join the threads on shutdown.
+  std::set<WorkerThread*> workers_;
+  // Worker threads that have shut themselves down (due to being idle), and are
+  // awaiting cleanup by the master thread.
+  std::set<WorkerThread*> zombies_;
+  // How many workers do we have that are actually executing tasks?
+  unsigned int num_busy_workers_;
+  // We set this to true to tell the worker threads to terminate.
+  bool shutting_down_;
+  // The priority queue of pending tasks.  Invariant: all Function objects in
+  // the queue have neither been started nor cancelled yet.
+  TaskQueue task_queue_;
+  // This maps executors to the number of currently running tasks for that
+  // executor; we increment when we start a task, and decrement when we finish
+  // it.  If the number is zero, we remove the entry from the map; thus, as an
+  // invariant the map only contains entries for executors with active tasks.
+  OwnerMap active_task_counts_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadPool);
+};
+
+}  // namespace mod_spdy
+
+#endif  // MOD_SPDY_COMMON_THREAD_POOL_H_

Propchange: httpd/httpd/trunk/modules/spdy/common/thread_pool.h
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/httpd/trunk/modules/spdy/common/thread_pool_test.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/thread_pool_test.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/thread_pool_test.cc (added)
+++ httpd/httpd/trunk/modules/spdy/common/thread_pool_test.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,339 @@
+// Copyright 2012 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "mod_spdy/common/thread_pool.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/testing/notification.h"
+#include "net/instaweb/util/public/function.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// When adding tests here, try to keep them robust against thread scheduling
+// differences from run to run.  In particular, they shouldn't fail just
+// because you're running under Valgrind.
+
+namespace {
+
+// When run, a TestFunction waits for `wait` millis, then sets `*result` to
+// RAN.  When cancelled, it sets *result to CANCELLED.
+class TestFunction : public net_instaweb::Function {
+ public:
+  enum Result { NOTHING, RAN, CANCELLED };
+  TestFunction(int wait, base::Lock* lock, Result* result)
+      : wait_(wait), lock_(lock), result_(result) {}
+  virtual ~TestFunction() {}
+ protected:
+  // net_instaweb::Function methods:
+  virtual void Run() {
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(wait_));
+    base::AutoLock autolock(*lock_);
+    *result_ = RAN;
+  }
+  virtual void Cancel() {
+    base::AutoLock autolock(*lock_);
+    *result_ = CANCELLED;
+  }
+ private:
+  const int wait_;
+  base::Lock* const lock_;
+  Result* const result_;
+  DISALLOW_COPY_AND_ASSIGN(TestFunction);
+};
+
+// Test that we execute tasks concurrently, that that we respect priorities
+// when pulling tasks from the queue.
+TEST(ThreadPoolTest, ConcurrencyAndPrioritization) {
+  // Create a thread pool with 2 threads, and an executor.
+  mod_spdy::ThreadPool thread_pool(2, 2);
+  ASSERT_TRUE(thread_pool.Start());
+  scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+  base::Lock lock;
+  TestFunction::Result result0 = TestFunction::NOTHING;
+  TestFunction::Result result1 = TestFunction::NOTHING;
+  TestFunction::Result result2 = TestFunction::NOTHING;
+  TestFunction::Result result3 = TestFunction::NOTHING;
+
+  // Create a high-priority TestFunction, which waits for 200 millis then
+  // records that it ran.
+  executor->AddTask(new TestFunction(200, &lock, &result0), 0);
+  // Create several TestFunctions at different priorities.  Each waits 100
+  // millis then records that it ran.
+  executor->AddTask(new TestFunction(100, &lock, &result1), 1);
+  executor->AddTask(new TestFunction(100, &lock, &result3), 3);
+  executor->AddTask(new TestFunction(100, &lock, &result2), 2);
+
+  // Wait 150 millis, then stop the executor.
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(150));
+  executor->Stop();
+
+  // Only TestFunctions that _started_ within the first 150 millis should have
+  // run; the others should have been cancelled.
+  //   - The priority-0 function should have started first, on the first
+  //     thread.  It finishes after 200 millis.
+  //   - The priority-1 function should run on the second thread.  It finishes
+  //     after 100 millis.
+  //   - The priority-2 function should run on the second thread after the
+  //     priority-1 function finishes, even though it was pushed last, because
+  //     it's higher-priority than the priority-3 function.  It finishes at the
+  //     200-milli mark.
+  //   - The priority-3 function should not get a chance to run, because we
+  //     stop the executor after 150 millis, and the soonest it could start is
+  //     the 200-milli mark.
+  base::AutoLock autolock(lock);
+  EXPECT_EQ(TestFunction::RAN, result0);
+  EXPECT_EQ(TestFunction::RAN, result1);
+  EXPECT_EQ(TestFunction::RAN, result2);
+  EXPECT_EQ(TestFunction::CANCELLED, result3);
+}
+
+// Test that stopping one executor doesn't affect tasks on another executor
+// from the same ThreadPool.
+TEST(ThreadPoolTest, MultipleExecutors) {
+  // Create a thread pool with 3 threads, and two executors.
+  mod_spdy::ThreadPool thread_pool(3, 3);
+  ASSERT_TRUE(thread_pool.Start());
+  scoped_ptr<mod_spdy::Executor> executor1(thread_pool.NewExecutor());
+  scoped_ptr<mod_spdy::Executor> executor2(thread_pool.NewExecutor());
+
+  base::Lock lock;
+  TestFunction::Result e1r1 = TestFunction::NOTHING;
+  TestFunction::Result e1r2 = TestFunction::NOTHING;
+  TestFunction::Result e1r3 = TestFunction::NOTHING;
+  TestFunction::Result e2r1 = TestFunction::NOTHING;
+  TestFunction::Result e2r2 = TestFunction::NOTHING;
+  TestFunction::Result e2r3 = TestFunction::NOTHING;
+
+  // Add some tasks to the executors.  Each one takes 50 millis to run.
+  executor1->AddTask(new TestFunction(50, &lock, &e1r1), 0);
+  executor2->AddTask(new TestFunction(50, &lock, &e2r1), 0);
+  executor1->AddTask(new TestFunction(50, &lock, &e1r2), 0);
+  executor2->AddTask(new TestFunction(50, &lock, &e2r2), 1);
+  executor1->AddTask(new TestFunction(50, &lock, &e1r3), 3);
+  executor2->AddTask(new TestFunction(50, &lock, &e2r3), 1);
+
+  // Wait 20 millis (to make sure the first few tasks got picked up), then
+  // destroy executor2, which should stop it.  Finally, sleep another 100
+  // millis to give the remaining tasks a chance to finish.
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+  executor2.reset();
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+
+  // The three high priority tasks should have all run.  The other two tasks on
+  // executor2 should have been cancelled when we stopped executor2, but the
+  // low-priority task on executor1 should have been left untouched, and
+  // allowed to finish.
+  base::AutoLock autolock(lock);
+  EXPECT_EQ(TestFunction::RAN, e1r1);
+  EXPECT_EQ(TestFunction::RAN, e2r1);
+  EXPECT_EQ(TestFunction::RAN, e1r2);
+  EXPECT_EQ(TestFunction::CANCELLED, e2r2);
+  EXPECT_EQ(TestFunction::RAN, e1r3);
+  EXPECT_EQ(TestFunction::CANCELLED, e2r3);
+}
+
+// When run, a WaitFunction blocks until the notification is set.
+class WaitFunction : public net_instaweb::Function {
+ public:
+  WaitFunction(mod_spdy::testing::Notification* notification)
+      : notification_(notification) {}
+  virtual ~WaitFunction() {}
+ protected:
+  // net_instaweb::Function methods:
+  virtual void Run() {
+    notification_->Wait();
+  }
+  virtual void Cancel() {}
+ private:
+  mod_spdy::testing::Notification* const notification_;
+  DISALLOW_COPY_AND_ASSIGN(WaitFunction);
+};
+
+// When run, an IdFunction pushes its ID onto the vector.
+class IdFunction : public net_instaweb::Function {
+ public:
+  IdFunction(int id, base::Lock* lock, base::ConditionVariable* condvar,
+             std::vector<int>* output)
+      : id_(id), lock_(lock), condvar_(condvar), output_(output) {}
+  virtual ~IdFunction() {}
+ protected:
+  // net_instaweb::Function methods:
+  virtual void Run() {
+    base::AutoLock autolock(*lock_);
+    output_->push_back(id_);
+    condvar_->Broadcast();
+  }
+  virtual void Cancel() {}
+ private:
+  const int id_;
+  base::Lock* const lock_;
+  base::ConditionVariable* const condvar_;
+  std::vector<int>* const output_;
+  DISALLOW_COPY_AND_ASSIGN(IdFunction);
+};
+
+// Test that if many tasks of the same priority are added, they are run in the
+// order they were added.
+TEST(ThreadPoolTest, SamePriorityTasksAreFIFO) {
+  // Create a thread pool with just one thread, and an executor.
+  mod_spdy::ThreadPool thread_pool(1, 1);
+  ASSERT_TRUE(thread_pool.Start());
+  scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+  // First, make sure no other tasks will get started until we set the
+  // notification.
+  mod_spdy::testing::Notification start;
+  executor->AddTask(new WaitFunction(&start), 0);
+
+  // Add many tasks to the executor, of varying priorities.
+  const int num_tasks_each_priority = 1000;
+  const int total_num_tasks = 3 * num_tasks_each_priority;
+  base::Lock lock;
+  base::ConditionVariable condvar(&lock);
+  std::vector<int> ids;  // protected by lock
+  for (int id = 0; id < num_tasks_each_priority; ++id) {
+    executor->AddTask(new IdFunction(id, &lock, &condvar, &ids), 1);
+    executor->AddTask(new IdFunction(id + num_tasks_each_priority,
+                                     &lock, &condvar, &ids), 2);
+    executor->AddTask(new IdFunction(id + 2 * num_tasks_each_priority,
+                                     &lock, &condvar, &ids), 3);
+  }
+
+  // Start us off, then wait for all tasks to finish.
+  start.Set();
+  base::AutoLock autolock(lock);
+  while (static_cast<int>(ids.size()) < total_num_tasks) {
+    condvar.Wait();
+  }
+
+  // Check that the tasks were executed in order by the one worker thread.
+  for (int index = 0; index < total_num_tasks; ++index) {
+    ASSERT_EQ(index, ids[index])
+        << "Task " << ids[index] << " finished in position " << index;
+  }
+}
+
+// Add a test failure if the thread pool does not stabilize to the expected
+// total/idle number of worker threads withing the given timeout.
+void ExpectWorkersWithinTimeout(int expected_num_workers,
+                                int expected_num_idle_workers,
+                                mod_spdy::ThreadPool* thread_pool,
+                                int timeout_millis) {
+  int millis_remaining = timeout_millis;
+  while (true) {
+    const int actual_num_workers = thread_pool->GetNumWorkersForTest();
+    const int actual_num_idle_workers =
+        thread_pool->GetNumIdleWorkersForTest();
+    if (actual_num_workers == expected_num_workers &&
+        actual_num_idle_workers == expected_num_idle_workers) {
+      return;
+    }
+    if (millis_remaining <= 0) {
+      ADD_FAILURE() << "Timed out; expected " << expected_num_workers
+                    << " worker(s) with " << expected_num_idle_workers
+                    <<" idle; still at " << actual_num_workers
+                    << " worker(s) with " << actual_num_idle_workers
+                    << " idle after " << timeout_millis << "ms";
+      return;
+    }
+    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+    millis_remaining -= 10;
+  }
+}
+
+// Test that we spawn new threads as needed, and allow them to die off after
+// being idle for a while.
+TEST(ThreadPoolTest, CreateAndRetireWorkers) {
+  // Create a thread pool with min_threads < max_threads, and give it a short
+  // max_thread_idle_time.
+  const int idle_time_millis = 100;
+  mod_spdy::ThreadPool thread_pool(
+      2, 4, base::TimeDelta::FromMilliseconds(idle_time_millis));
+  ASSERT_TRUE(thread_pool.Start());
+  // As soon as we start the thread pool, there should be the minimum number of
+  // workers (two), both counted as idle.
+  EXPECT_EQ(2, thread_pool.GetNumWorkersForTest());
+  EXPECT_EQ(2, thread_pool.GetNumIdleWorkersForTest());
+
+  scoped_ptr<mod_spdy::Executor> executor(thread_pool.NewExecutor());
+
+  // Start up three tasks.  That should push us up to three workers
+  // immediately.  If we make sure to give those threads a chance to run, they
+  // should soon pick up the tasks and all be busy.
+  mod_spdy::testing::Notification done1;
+  executor->AddTask(new WaitFunction(&done1), 0);
+  executor->AddTask(new WaitFunction(&done1), 1);
+  executor->AddTask(new WaitFunction(&done1), 2);
+  EXPECT_EQ(3, thread_pool.GetNumWorkersForTest());
+  ExpectWorkersWithinTimeout(3, 0, &thread_pool, 100);
+
+  // Add three more tasks.  We should now be at the maximum number of workers,
+  // and that fourth worker should be busy soon.
+  mod_spdy::testing::Notification done2;
+  executor->AddTask(new WaitFunction(&done2), 1);
+  executor->AddTask(new WaitFunction(&done2), 2);
+  executor->AddTask(new WaitFunction(&done2), 3);
+  EXPECT_EQ(4, thread_pool.GetNumWorkersForTest());
+  ExpectWorkersWithinTimeout(4, 0, &thread_pool, 100);
+
+  // Allow the first group of tasks to finish.  There are now only three tasks
+  // running, so one of our four threads should go idle.  If we wait for a
+  // while after that, that thread should terminate and enter zombie mode.
+  done1.Set();
+  ExpectWorkersWithinTimeout(4, 1, &thread_pool, idle_time_millis / 2);
+  ExpectWorkersWithinTimeout(3, 0, &thread_pool, 2 * idle_time_millis);
+  EXPECT_EQ(1, thread_pool.GetNumZombiesForTest());
+
+  // Allow the second group of tasks to finish.  There are no tasks left, so
+  // all three threads should go idle.  If we wait for a while after that,
+  // exactly one of the three should shut down, bringing us back down to the
+  // minimum number of threads.  We should now have two zombie threads.
+  done2.Set();
+  ExpectWorkersWithinTimeout(3, 3, &thread_pool, idle_time_millis / 2);
+  ExpectWorkersWithinTimeout(2, 2, &thread_pool, 2 * idle_time_millis);
+  EXPECT_EQ(2, thread_pool.GetNumZombiesForTest());
+
+  // Start some new new tasks.  This should cause us to immediately reap the
+  // zombie threads, and soon, we should have three busy threads.
+  mod_spdy::testing::Notification done3;
+  executor->AddTask(new WaitFunction(&done3), 0);
+  executor->AddTask(new WaitFunction(&done3), 2);
+  executor->AddTask(new WaitFunction(&done3), 1);
+  EXPECT_EQ(0, thread_pool.GetNumZombiesForTest());
+  EXPECT_EQ(3, thread_pool.GetNumWorkersForTest());
+  ExpectWorkersWithinTimeout(3, 0, &thread_pool, 100);
+
+  // Let those tasks finish.  Once again, the threads should go idle, and then
+  // one of them should terminate and enter zombie mode.
+  done3.Set();
+  ExpectWorkersWithinTimeout(3, 3, &thread_pool, idle_time_millis / 2);
+  ExpectWorkersWithinTimeout(2, 2, &thread_pool, 2 * idle_time_millis);
+  EXPECT_EQ(1, thread_pool.GetNumZombiesForTest());
+
+  // When we exit the test, the thread pool's destructor should reap the zombie
+  // thread (as well as shutting down the still-running workers).  We can
+  // verify this by running this test under valgrind and making sure that no
+  // memory is leaked.
+}
+
+}  // namespace

Added: httpd/httpd/trunk/modules/spdy/common/version.h.in
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/common/version.h.in?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/common/version.h.in (added)
+++ httpd/httpd/trunk/modules/spdy/common/version.h.in Mon Apr 28 10:55:17 2014
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors.  All rights reserved.
+// Use of this source is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// version.h is generated from version.h.in.  Edit the source!
+
+#pragma once
+
+// Version Information
+
+#define MOD_SPDY_VERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+#define MOD_SPDY_VERSION_STRING "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+
+// Branding Information
+
+#define COMPANY_FULLNAME_STRING "@COMPANY_FULLNAME@"
+#define COMPANY_SHORTNAME_STRING "@COMPANY_SHORTNAME@"
+#define PRODUCT_FULLNAME_STRING "@PRODUCT_FULLNAME@"
+#define PRODUCT_SHORTNAME_STRING "@PRODUCT_SHORTNAME@"
+#define COPYRIGHT_STRING "@COPYRIGHT@"
+#define OFFICIAL_BUILD_STRING "@OFFICIAL_BUILD@"
+
+// Changelist Information
+
+#define LASTCHANGE_STRING "@LASTCHANGE@"

Propchange: httpd/httpd/trunk/modules/spdy/common/version.h.in
------------------------------------------------------------------------------
    svn:eol-style = native

Added: httpd/httpd/trunk/modules/spdy/mod_spdy.cc
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/mod_spdy.cc?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/mod_spdy.cc (added)
+++ httpd/httpd/trunk/modules/spdy/mod_spdy.cc Mon Apr 28 10:55:17 2014
@@ -0,0 +1,850 @@
+// Copyright 2010 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// References to "TAMB" below refer to _The Apache Modules Book_ by Nick Kew
+// (ISBN: 0-13-240967-4).
+
+#include "mod_spdy/mod_spdy.h"
+
+#include <algorithm>  // for std::min
+
+#include "httpd.h"
+#include "http_connection.h"
+#include "http_config.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "apr_optional.h"
+#include "apr_optional_hooks.h"
+#include "apr_tables.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mod_spdy/apache/apache_spdy_session_io.h"
+#include "mod_spdy/apache/apache_spdy_stream_task_factory.h"
+#include "mod_spdy/apache/config_commands.h"
+#include "mod_spdy/apache/config_util.h"
+#include "mod_spdy/apache/id_pool.h"
+#include "mod_spdy/apache/filters/server_push_filter.h"
+#include "mod_spdy/apache/log_message_handler.h"
+#include "mod_spdy/apache/master_connection_context.h"
+#include "mod_spdy/apache/pool_util.h"
+#include "mod_spdy/apache/slave_connection_context.h"
+#include "mod_spdy/apache/slave_connection_api.h"
+#include "mod_spdy/apache/ssl_util.h"
+#include "mod_spdy/common/executor.h"
+#include "mod_spdy/common/protocol_util.h"
+#include "mod_spdy/common/spdy_server_config.h"
+#include "mod_spdy/common/spdy_session.h"
+#include "mod_spdy/common/thread_pool.h"
+#include "mod_spdy/common/version.h"
+
+extern "C" {
+
+// Declaring mod_so's optional hooks here (so that we don't need to
+// #include "mod_so.h").
+APR_DECLARE_OPTIONAL_FN(module*, ap_find_loaded_module_symbol,
+                        (server_rec*, const char*));
+
+// Declaring modified mod_ssl's optional hooks here (so that we don't need to
+// #include "mod_ssl.h").
+APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_advertise_protos_hook,
+                          (conn_rec *connection, apr_array_header_t *protos));
+APR_DECLARE_EXTERNAL_HOOK(modssl, AP, int, npn_proto_negotiated_hook,
+                          (conn_rec *connection, const char *proto_name,
+                           apr_size_t proto_name_len));
+
+}  // extern "C"
+
+namespace {
+
+const char kFakeModSpdyProtocolName[] =
+    "x-mod-spdy/" MOD_SPDY_VERSION_STRING "-" LASTCHANGE_STRING;
+COMPILE_ASSERT(arraysize(kFakeModSpdyProtocolName) <= 255,
+               fake_protocol_name_is_not_too_long_for_npn);
+const char kFakeModSpdyProtocolNameNoVersion[] = "x-mod-spdy/no-version";
+COMPILE_ASSERT(arraysize(kFakeModSpdyProtocolNameNoVersion) <= 255,
+               fake_protocol_name_no_version_is_not_too_long_for_npn);
+
+const char* const kHttpProtocolName = "http/1.1";
+const char* const kSpdy2ProtocolName = "spdy/2";
+const char* const kSpdy3ProtocolName = "spdy/3";
+const char* const kSpdy31ProtocolName = "spdy/3.1";
+const char* const kSpdyVersionEnvironmentVariable = "SPDY_VERSION";
+
+const char* const kPhpModuleNames[] = {
+  "php_module",
+  "php2_module",
+  "php3_module",
+  "php4_module",
+  "php5_module",
+  "php6_module"
+};
+
+// This global variable stores the filter handle for our push filter.  Normally,
+// global variables would be very dangerous in a concurrent environment like
+// Apache, but this one is okay because it is assigned just once, at
+// start-up (during which Apache is running single-threaded; see TAMB 2.2.1),
+// and are read-only thereafter.
+ap_filter_rec_t* gServerPushFilterHandle = NULL;
+
+// A process-global thread pool for processing SPDY streams concurrently.  This
+// is initialized once in *each child process* by our child-init hook.  Note
+// that in a non-threaded MPM (e.g. Prefork), this thread pool will be used by
+// just one SPDY connection at a time, but in a threaded MPM (e.g. Worker) it
+// will shared by several SPDY connections at once.  That's okay though,
+// because ThreadPool objects are thread-safe.  Users just have to make sure
+// that they configure SpdyMaxThreadsPerProcess depending on the MPM.
+mod_spdy::ThreadPool* gPerProcessThreadPool = NULL;
+
+// Optional function provided by mod_spdy.  Return zero if the connection is
+// not using SPDY, otherwise return the SPDY version number in use.  Note that
+// unlike our private functions, we use Apache C naming conventions for this
+// function because we export it to other modules.
+int spdy_get_version(conn_rec* connection) {
+  if (mod_spdy::HasMasterConnectionContext(connection)) {
+    mod_spdy::MasterConnectionContext* master_context =
+        mod_spdy::GetMasterConnectionContext(connection);
+    if (master_context->is_using_spdy()) {
+      return mod_spdy::SpdyVersionToFramerVersion(
+          master_context->spdy_version());
+    }
+  }
+
+  if (mod_spdy::HasSlaveConnectionContext(connection)) {
+    mod_spdy::SlaveConnectionContext* slave_context =
+        mod_spdy::GetSlaveConnectionContext(connection);
+    if (slave_context->spdy_version() != mod_spdy::spdy::SPDY_VERSION_NONE) {
+      return mod_spdy::SpdyVersionToFramerVersion(
+          slave_context->spdy_version());
+    }
+  }
+
+  return 0;
+}
+
+apr_status_t ServerPushFilterFunc(ap_filter_t* filter,
+                                  apr_bucket_brigade* input_brigade) {
+  mod_spdy::ServerPushFilter* server_push_filter =
+      static_cast<mod_spdy::ServerPushFilter*>(filter->ctx);
+  return server_push_filter->Write(filter, input_brigade);
+}
+
+// Called on server startup, after all modules have loaded.
+void RetrieveOptionalFunctions() {
+  mod_spdy::RetrieveModSslFunctions();
+}
+
+// Called after configuration has completed.
+int PostConfig(apr_pool_t* pconf, apr_pool_t* plog, apr_pool_t* ptemp,
+               server_rec* server_list) {
+  mod_spdy::ScopedServerLogHandler log_handler(server_list);
+
+  // Check if any of the virtual hosts have mod_spdy enabled.
+  bool any_enabled = false;
+  for (server_rec* server = server_list; server != NULL;
+       server = server->next) {
+    if (mod_spdy::GetServerConfig(server)->spdy_enabled()) {
+      any_enabled = true;
+      break;
+    }
+  }
+
+  // Log a message indicating whether mod_spdy is enabled or not.  It's all too
+  // easy to install mod_spdy and forget to turn it on, so this may be helpful
+  // for debugging server behavior.
+  if (!any_enabled) {
+    LOG(WARNING) << "mod_spdy is installed, but has not been enabled in the "
+                 << "Apache config. SPDY will not be used by this server.  "
+                 << "See http://code.google.com/p/mod-spdy/wiki/ConfigOptions "
+                 << "for how to enable.";
+  }
+
+
+  // Modules which may not be thread-safe shouldn't be used with mod_spdy.
+  // That mainly seems to be mod_php.  If mod_php is installed, log a warning
+  // pointing the user to docs on how to use PHP safely with mod_spdy.
+  if (any_enabled) {
+    module* (*get_module)(server_rec*, const char*) =
+        APR_RETRIEVE_OPTIONAL_FN(ap_find_loaded_module_symbol);
+    if (get_module != NULL) {
+      for (size_t i = 0; i < arraysize(kPhpModuleNames); ++i) {
+        if (get_module(server_list, kPhpModuleNames[i]) != NULL) {
+          LOG(WARNING)
+              << kPhpModuleNames[i] << " may not be thread-safe, and "
+              << "should not be used with mod_spdy.  Instead, see "
+              << "https://developers.google.com/speed/spdy/mod_spdy/php for "
+              << "how to configure your server to use PHP safely.";
+        }
+      }
+    }
+  }
+
+  return OK;
+}
+
+// Called exactly once for each child process, before that process starts
+// spawning worker threads.
+void ChildInit(apr_pool_t* pool, server_rec* server_list) {
+  mod_spdy::ScopedServerLogHandler log_handler(server_list);
+
+  // Check whether mod_spdy is enabled for any server_rec in the list, and
+  // determine the most verbose log level of any server in the list.
+  bool spdy_enabled = false;
+  int max_apache_log_level = APLOG_EMERG;  // the least verbose log level
+  COMPILE_ASSERT(APLOG_INFO > APLOG_ERR, bigger_number_means_more_verbose);
+  for (server_rec* server = server_list; server != NULL;
+       server = server->next) {
+    spdy_enabled |= mod_spdy::GetServerConfig(server)->spdy_enabled();
+    if (server->loglevel > max_apache_log_level) {
+      max_apache_log_level = server->loglevel;
+    }
+  }
+
+  // There are a couple config options we need to check (vlog_level and
+  // max_threads_per_process) that are only settable at the top level of the
+  // config, so it doesn't matter which server in the list we read them from.
+  const mod_spdy::SpdyServerConfig* top_level_config =
+      mod_spdy::GetServerConfig(server_list);
+
+  // We set mod_spdy's global logging level to that of the most verbose server
+  // in the list.  The scoped logging handlers we establish will sometimes
+  // restrict things further, if they are for a less verbose virtual host.
+  mod_spdy::SetLoggingLevel(max_apache_log_level,
+                            top_level_config->vlog_level());
+
+  // If mod_spdy is not enabled on any server_rec, don't do any other setup.
+  if (!spdy_enabled) {
+    return;
+  }
+
+  // Create the per-process thread pool.
+  const int max_threads = top_level_config->max_threads_per_process();
+  const int min_threads =
+      std::min(max_threads, top_level_config->min_threads_per_process());
+  scoped_ptr<mod_spdy::ThreadPool> thread_pool(
+      new mod_spdy::ThreadPool(min_threads, max_threads));
+  if (thread_pool->Start()) {
+    gPerProcessThreadPool = thread_pool.release();
+    mod_spdy::PoolRegisterDelete(pool, gPerProcessThreadPool);
+  } else {
+    LOG(DFATAL) << "Could not create mod_spdy thread pool; "
+                << "mod_spdy will not function.";
+  }
+}
+
+// A pre-connection hook, to be run _before_ mod_ssl's pre-connection hook.
+// Disables mod_ssl for our slave connections.
+int DisableSslForSlaves(conn_rec* connection, void* csd) {
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+    // For master connections, the context object should't have been created
+    // yet (it gets created in PreConnection).
+    DCHECK(!mod_spdy::HasMasterConnectionContext(connection));
+    return DECLINED;  // only do things for slave connections.
+  }
+
+  // If a slave context has already been created, mod_spdy must be enabled.
+  DCHECK(mod_spdy::GetServerConfig(connection)->spdy_enabled());
+
+  // Disable mod_ssl for the slave connection so it doesn't get in our way.
+  if (!mod_spdy::DisableSslForConnection(connection)) {
+    // Hmm, mod_ssl either isn't installed or isn't enabled.  That should be
+    // impossible (we wouldn't _have_ a slave connection without having SSL for
+    // the master connection), unless we're configured to assume SPDY for
+    // non-SSL connections.  Let's check if that's the case, and LOG(DFATAL) if
+    // it's not.
+    if (mod_spdy::GetServerConfig(connection)->
+        use_spdy_version_without_ssl() == mod_spdy::spdy::SPDY_VERSION_NONE) {
+      LOG(DFATAL) << "mod_ssl missing for slave connection";
+    }
+  }
+  return OK;
+}
+
+// A pre-connection hook, to be run _after_ mod_ssl's pre-connection hook, but
+// just _before_ the core pre-connection hook.  For master connections, this
+// checks if SSL is active; for slave connections, this adds our
+// connection-level filters and prevents core filters from being inserted.
+int PreConnection(conn_rec* connection, void* csd) {
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  // If a slave context has not yet been created, this is a "real" connection.
+  if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+    // Master context should not have been created yet, either.
+    DCHECK(!mod_spdy::HasMasterConnectionContext(connection));
+
+    // If mod_spdy is disabled on this server, don't allocate our context
+    // object.
+    const mod_spdy::SpdyServerConfig* config =
+        mod_spdy::GetServerConfig(connection);
+    if (!config->spdy_enabled()) {
+      return DECLINED;
+    }
+
+    // We'll set this to a nonzero SPDY version number momentarily if we're
+    // configured to assume a particular SPDY version for this connection.
+    mod_spdy::spdy::SpdyVersion assume_spdy_version =
+        mod_spdy::spdy::SPDY_VERSION_NONE;
+
+    // Check if this connection is over SSL; if not, we can't do NPN, so we
+    // definitely won't be using SPDY (unless we're configured to assume SPDY
+    // for non-SSL connections).
+    const bool using_ssl = mod_spdy::IsUsingSslForConnection(connection);
+    if (!using_ssl) {
+      // This is not an SSL connection, so we can't talk SPDY on it _unless_ we
+      // have opted to assume SPDY over non-SSL connections (presumably for
+      // debugging purposes; this would normally break browsers).
+      assume_spdy_version = config->use_spdy_version_without_ssl();
+      if (assume_spdy_version == mod_spdy::spdy::SPDY_VERSION_NONE) {
+        return DECLINED;
+      }
+    }
+
+    // Okay, we've got a real connection over SSL, so we'll be negotiating with
+    // the client to see if we can use SPDY for this connection.  Create our
+    // connection context object to keep track of the negotiation.
+    mod_spdy::MasterConnectionContext* master_context =
+        mod_spdy::CreateMasterConnectionContext(connection, using_ssl);
+    // If we're assuming SPDY for this connection, it means we know NPN won't
+    // happen at all, and we're just going to assume a particular SPDY version.
+    if (assume_spdy_version != mod_spdy::spdy::SPDY_VERSION_NONE) {
+      master_context->set_assume_spdy(true);
+      master_context->set_spdy_version(assume_spdy_version);
+    }
+    return OK;
+  }
+  // If the context has already been created, this is a slave connection.
+  else {
+    mod_spdy::SlaveConnectionContext* slave_context =
+        mod_spdy::GetSlaveConnectionContext(connection);
+
+    DCHECK(mod_spdy::GetServerConfig(connection)->spdy_enabled());
+
+    // Add our input and output filters.
+    ap_add_input_filter_handle(
+        slave_context->input_filter_handle(),  // filter handle
+        slave_context->input_filter_context(), // context (any void* we want)
+        NULL,                     // request object
+        connection);              // connection object
+
+    ap_add_output_filter_handle(
+        slave_context->output_filter_handle(),    // filter handle
+        slave_context->output_filter_context(),   // context (any void* we want)
+        NULL,                       // request object
+        connection);                // connection object
+
+    // Prevent core pre-connection hooks from running (thus preventing core
+    // filters from being inserted).
+    return DONE;
+  }
+}
+
+// Called to see if we want to take care of processing this connection -- if
+// so, we do so and return OK, otherwise we return DECLINED.  For slave
+// connections, we want to return DECLINED.  For "real" connections, we need to
+// determine if they are using SPDY; if not we returned DECLINED, but if so we
+// process this as a master SPDY connection and then return OK.
+int ProcessConnection(conn_rec* connection) {
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  // If mod_spdy is disabled on this server, don't use SPDY.
+  const mod_spdy::SpdyServerConfig* config =
+      mod_spdy::GetServerConfig(connection);
+  if (!config->spdy_enabled()) {
+    return DECLINED;
+  }
+
+  // We do not want to attach to non-inbound connections (e.g. connections
+  // created by mod_proxy).  Non-inbound connections do not get a scoreboard
+  // hook, so we abort if the connection doesn't have the scoreboard hook.  See
+  // http://mail-archives.apache.org/mod_mbox/httpd-dev/201008.mbox/%3C99EA83DCDE961346AFA9B5EC33FEC08B047FDC26@VF-MBX11.internal.vodafone.com%3E
+  // for more details.
+  if (connection->sbh == NULL) {
+    return DECLINED;
+  }
+
+  // Our connection context object will have been created by now, unless our
+  // pre-connection hook saw that this was a non-SSL connection, in which case
+  // we won't be using SPDY so we can stop now. It may also mean that this is
+  // a slave connection, in which case we don't want to deal with it here --
+  // instead we will let Apache treat it like a regular HTTP connection.
+  if (!mod_spdy::HasMasterConnectionContext(connection)) {
+    return DECLINED;
+  }
+
+  mod_spdy::MasterConnectionContext* master_context =
+      mod_spdy::GetMasterConnectionContext(connection);
+
+  // In the unlikely event that we failed to create our per-process thread
+  // pool, we're not going to be able to operate.
+  if (gPerProcessThreadPool == NULL) {
+    return DECLINED;
+  }
+
+  // Unless we're simply assuming SPDY for this connection, we need to do NPN
+  // to decide whether to use SPDY or not.
+  if (!master_context->is_assuming_spdy()) {
+    // We need to pull some data through mod_ssl in order to force the SSL
+    // handshake, and hence NPN, to take place.  To that end, perform a small
+    // SPECULATIVE read (and then throw away whatever data we got).
+    apr_bucket_brigade* temp_brigade =
+        apr_brigade_create(connection->pool, connection->bucket_alloc);
+    const apr_status_t status =
+        ap_get_brigade(connection->input_filters, temp_brigade,
+                       AP_MODE_SPECULATIVE, APR_BLOCK_READ, 1);
+    apr_brigade_destroy(temp_brigade);
+
+    // If we were unable to pull any data through, give up and return DECLINED.
+    if (status != APR_SUCCESS) {
+      // Depending on exactly what went wrong, we may want to log something
+      // before returning DECLINED.
+      if (APR_STATUS_IS_EOF(status)) {
+        // EOF errors are to be expected sometimes (e.g. if the connection was
+        // closed), and we should just quietly give up.  No need to log in this
+        // case.
+      } else if (APR_STATUS_IS_TIMEUP(status)) {
+        // TIMEUP errors also seem to happen occasionally.  I think we should
+        // also give up in this case, but I'm not sure yet; for now let's VLOG
+        // when it happens, to help with debugging [mdsteele].
+        VLOG(1) << "Couldn't read from SSL connection (TIMEUP).";
+      } else {
+        // Any other error could be a real issue, so let's log it (slightly)
+        // more noisily.
+        LOG(INFO) << "Couldn't read from SSL connection; failed with status "
+                  << status << ": " << mod_spdy::AprStatusString(status);
+      }
+      return DECLINED;
+    }
+
+    // If we did pull some data through, then NPN should have happened and our
+    // OnNextProtocolNegotiated() hook should have been called by now.  If NPN
+    // hasn't happened, it's probably because we're using an old version of
+    // mod_ssl that doesn't support NPN, in which case we should probably warn
+    // the user that mod_spdy isn't going to work.
+    if (master_context->npn_state() ==
+        mod_spdy::MasterConnectionContext::NOT_DONE_YET) {
+      LOG(WARNING)
+          << "NPN didn't happen during SSL handshake.  You're probably using "
+          << "a version of mod_ssl that doesn't support NPN. Without NPN "
+          << "support, the server cannot use SPDY. See "
+          << "http://code.google.com/p/mod-spdy/wiki/GettingStarted for more "
+          << "information on installing a version of mod_spdy with NPN "
+          << "support.";
+    }
+  }
+
+  // If NPN didn't choose SPDY, then don't use SPDY.
+  if (!master_context->is_using_spdy()) {
+    return DECLINED;
+  }
+
+  const mod_spdy::spdy::SpdyVersion spdy_version =
+      master_context->spdy_version();
+  LOG(INFO) << "Starting SPDY/" <<
+      mod_spdy::SpdyVersionNumberString(spdy_version) << " session";
+
+  // At this point, we and the client have agreed to use SPDY (either that, or
+  // we've been configured to use SPDY regardless of what the client says), so
+  // process this as a SPDY master connection.
+  mod_spdy::ApacheSpdySessionIO session_io(connection);
+  mod_spdy::ApacheSpdyStreamTaskFactory task_factory(connection);
+  scoped_ptr<mod_spdy::Executor> executor(
+      gPerProcessThreadPool->NewExecutor());
+  mod_spdy::SpdySession spdy_session(
+      spdy_version, config, &session_io, &task_factory, executor.get());
+  // This call will block until the session has closed down.
+  spdy_session.Run();
+
+  LOG(INFO) << "Terminating SPDY/" <<
+      mod_spdy::SpdyVersionNumberString(spdy_version) << " session";
+
+  // Return OK to tell Apache that we handled this connection.
+  return OK;
+}
+
+// Called by mod_ssl when it needs to decide what protocols to advertise to the
+// client during Next Protocol Negotiation (NPN).
+int AdvertiseSpdy(conn_rec* connection, apr_array_header_t* protos) {
+  // If mod_spdy is disabled on this server, then we shouldn't advertise SPDY
+  // to the client.
+  if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+    return DECLINED;
+  }
+
+  // Advertise SPDY to the client.  We push protocol names in descending order
+  // of preference; the one we'd most prefer comes first.
+  APR_ARRAY_PUSH(protos, const char*) = kSpdy31ProtocolName;
+  APR_ARRAY_PUSH(protos, const char*) = kSpdy3ProtocolName;
+  APR_ARRAY_PUSH(protos, const char*) = kSpdy2ProtocolName;
+  return OK;
+}
+
+// Called by mod_ssl (along with the AdvertiseSpdy function) when it needs to
+// decide what protocols to advertise to the client during Next Protocol
+// Negotiation (NPN).  These two functions are separate so that AdvertiseSpdy
+// can run early in the hook order, and AdvertiseHttp can run late.
+int AdvertiseHttp(conn_rec* connection, apr_array_header_t* protos) {
+  const mod_spdy::SpdyServerConfig* config =
+      mod_spdy::GetServerConfig(connection);
+  // If mod_spdy is disabled on this server, don't do anything.
+  if (!config->spdy_enabled()) {
+    return DECLINED;
+  }
+
+  // Apache definitely supports HTTP/1.1, and so it ought to advertise it
+  // during NPN.  However, the Apache core HTTP module doesn't yet know about
+  // this hook, so we advertise HTTP/1.1 for them.  But to be future-proof, we
+  // don't add "http/1.1" to the list if it's already there.
+  bool http_not_advertised = true;
+  for (int i = 0; i < protos->nelts; ++i) {
+    if (!strcmp(APR_ARRAY_IDX(protos, i, const char*), kHttpProtocolName)) {
+      http_not_advertised = false;
+      break;
+    }
+  }
+  if (http_not_advertised) {
+    // No one's advertised HTTP/1.1 yet, so let's do it now.
+    APR_ARRAY_PUSH(protos, const char*) = kHttpProtocolName;
+  }
+
+  // Advertise a fake protocol, indicating the mod_spdy version in use.  We
+  // push this last, so the client doesn't think we prefer it to HTTP.
+  if (config->send_version_header()) {
+    APR_ARRAY_PUSH(protos, const char*) = kFakeModSpdyProtocolName;
+  } else {
+    // If the user prefers not to send a version number, leave out the version
+    // number.
+    APR_ARRAY_PUSH(protos, const char*) = kFakeModSpdyProtocolNameNoVersion;
+  }
+
+  return OK;
+}
+
+// Called by mod_ssl after Next Protocol Negotiation (NPN) has completed,
+// informing us which protocol was chosen by the client.
+int OnNextProtocolNegotiated(conn_rec* connection, const char* proto_name,
+                             apr_size_t proto_name_len) {
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  // If mod_spdy is disabled on this server, then ignore the results of NPN.
+  if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+    return DECLINED;
+  }
+
+  // We disable mod_ssl for slave connections, so NPN shouldn't be happening
+  // unless this is a non-slave connection.
+  if (mod_spdy::HasSlaveConnectionContext(connection)) {
+    LOG(DFATAL) << "mod_ssl was aparently not disabled for slave connection";
+    return DECLINED;
+  }
+
+  // Given that mod_spdy is enabled, our context object should have already
+  // been created in our pre-connection hook, unless this is a non-SSL
+  // connection.  But if it's a non-SSL connection, then NPN shouldn't be
+  // happening, and this hook shouldn't be getting called!  So, let's
+  // LOG(DFATAL) if context is NULL here.
+  if (!mod_spdy::HasMasterConnectionContext(connection)) {
+    LOG(DFATAL) << "NPN happened, but there is no connection context.";
+    return DECLINED;
+  }
+
+  mod_spdy::MasterConnectionContext* master_context =
+      mod_spdy::GetMasterConnectionContext(connection);
+
+  // NPN should happen only once, so npn_state should still be NOT_DONE_YET.
+  if (master_context->npn_state() !=
+      mod_spdy::MasterConnectionContext::NOT_DONE_YET) {
+    LOG(DFATAL) << "NPN happened twice.";
+    return DECLINED;
+  }
+
+  // If the client chose the SPDY version that we advertised, then mark this
+  // connection as using SPDY.
+  const base::StringPiece protocol_name(proto_name, proto_name_len);
+  if (protocol_name == kSpdy2ProtocolName) {
+    master_context->set_npn_state(
+        mod_spdy::MasterConnectionContext::USING_SPDY);
+    master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_2);
+  } else if (protocol_name == kSpdy3ProtocolName) {
+    master_context->set_npn_state(
+        mod_spdy::MasterConnectionContext::USING_SPDY);
+    master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_3);
+  } else if (protocol_name == kSpdy31ProtocolName) {
+    master_context->set_npn_state(
+        mod_spdy::MasterConnectionContext::USING_SPDY);
+    master_context->set_spdy_version(mod_spdy::spdy::SPDY_VERSION_3_1);
+  }
+  // Otherwise, explicitly mark this connection as not using SPDY.
+  else {
+    master_context->set_npn_state(
+        mod_spdy::MasterConnectionContext::NOT_USING_SPDY);
+  }
+  return OK;
+}
+
+int SetUpSubprocessEnv(request_rec* request) {
+  conn_rec* connection = request->connection;
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  // If mod_spdy is disabled on this server, then don't do anything.
+  if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+    return DECLINED;
+  }
+
+  // Don't do anything unless this is a slave connection.
+  if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+    return DECLINED;
+  }
+
+  mod_spdy::SlaveConnectionContext* slave_context =
+      mod_spdy::GetSlaveConnectionContext(connection);
+
+  // If this request is over SPDY (which it might not be, if this slave
+  // connection is being used by another module through the slave connection
+  // API), then for the benefit of CGI scripts, which have no way of calling
+  // spdy_get_version(), set an environment variable indicating what SPDY
+  // version is being used, allowing them to optimize the response for SPDY.
+  // See http://code.google.com/p/mod-spdy/issues/detail?id=27 for details.
+  const mod_spdy::spdy::SpdyVersion spdy_version =
+      slave_context->spdy_version();
+  if (spdy_version != mod_spdy::spdy::SPDY_VERSION_NONE) {
+    apr_table_set(request->subprocess_env, kSpdyVersionEnvironmentVariable,
+                  mod_spdy::SpdyVersionNumberString(spdy_version));
+  }
+
+  // Normally, mod_ssl sets the HTTPS environment variable to "on" for requests
+  // served over SSL.  We turn mod_ssl off for our slave connections, but those
+  // requests _are_ (usually) being served over SSL (via the master
+  // connection), so we set the variable ourselves if we are in fact using SSL.
+  // See http://code.google.com/p/mod-spdy/issues/detail?id=32 for details.
+  if (slave_context->is_using_ssl()) {
+    apr_table_setn(request->subprocess_env, "HTTPS", "on");
+  }
+
+  return OK;
+}
+
+void InsertRequestFilters(request_rec* request) {
+  conn_rec* const connection = request->connection;
+  mod_spdy::ScopedConnectionLogHandler log_handler(connection);
+
+  // If mod_spdy is disabled on this server, then don't do anything.
+  if (!mod_spdy::GetServerConfig(connection)->spdy_enabled()) {
+    return;
+  }
+
+  // Don't do anything unless this is a slave connection.
+  if (!mod_spdy::HasSlaveConnectionContext(connection)) {
+    return;
+  }
+
+  mod_spdy::SlaveConnectionContext* slave_context =
+      mod_spdy::GetSlaveConnectionContext(connection);
+
+  // Insert a filter that will initiate server pushes when so instructed (such
+  // as by an X-Associated-Content header). This is conditional on this
+  // connection being managed entirely on mod_spdy, and not being done on
+  // behalf of someone else using the slave connection API.
+  if (slave_context->slave_stream() != NULL) {
+    mod_spdy::ServerPushFilter* server_push_filter =
+        new mod_spdy::ServerPushFilter(slave_context->slave_stream(), request,
+                                       mod_spdy::GetServerConfig(request));
+    PoolRegisterDelete(request->pool, server_push_filter);
+    ap_add_output_filter_handle(
+        gServerPushFilterHandle,  // filter handle
+        server_push_filter,       // context (any void* we want)
+        request,                  // request object
+        connection);              // connection object
+  }
+}
+
+apr_status_t InvokeIdPoolDestroyInstance(void*) {
+  mod_spdy::IdPool::DestroyInstance();
+  return APR_SUCCESS;
+}
+
+// Called when the module is loaded to register all of our hook functions.
+void RegisterHooks(apr_pool_t* pool) {
+  mod_spdy::InstallLogMessageHandler(pool);
+  mod_spdy::IdPool::CreateInstance();
+  apr_pool_cleanup_register(pool, NULL, InvokeIdPoolDestroyInstance,
+                            apr_pool_cleanup_null /* no cleanup on fork*/);
+
+  static const char* const modules_core[] = {"core.c", NULL};
+  static const char* const modules_mod_ssl[] = {"mod_ssl.c", NULL};
+
+  // Register a hook to be called after all modules have been loaded, so we can
+  // retrieve optional functions from mod_ssl.
+  ap_hook_optional_fn_retrieve(
+      RetrieveOptionalFunctions,  // hook function to be called
+      NULL,                       // predecessors
+      NULL,                       // successors
+      APR_HOOK_MIDDLE);           // position
+
+  // Register a hook to be called after configuration has completed.  We use
+  // this hook to log whether or not mod_spdy is enabled on this server.
+  ap_hook_post_config(PostConfig, NULL, NULL, APR_HOOK_MIDDLE);
+
+  // Register a hook to be called once for each child process spawned by
+  // Apache, before the MPM starts spawning worker threads.  We use this hook
+  // to initialize our per-process thread pool.
+  ap_hook_child_init(ChildInit, NULL, NULL, APR_HOOK_MIDDLE);
+
+  // Register a pre-connection hook to turn off mod_ssl for our slave
+  // connections.  This must run before mod_ssl's pre-connection hook, so that
+  // we can disable mod_ssl before it inserts its filters, so we name mod_ssl
+  // as an explicit successor.
+  ap_hook_pre_connection(
+      DisableSslForSlaves,        // hook function to be called
+      NULL,                       // predecessors
+      modules_mod_ssl,            // successors
+      APR_HOOK_FIRST);            // position
+
+  // Register our pre-connection hook, which will be called shortly before our
+  // process-connection hook.  The hooking order is very important here.  In
+  // particular:
+  //   * We must run before the core pre-connection hook, so that we can return
+  //     DONE and stop the core filters from being inserted.  Thus, we name
+  //     core.c as a successor.
+  //   * We should run after almost all other modules (except core.c) so that
+  //     our returning DONE doesn't prevent other modules from working.  Thus,
+  //     we use APR_HOOK_LAST for our position argument.
+  //   * In particular, we MUST run after mod_ssl's pre-connection hook, so
+  //     that we can ask mod_ssl if this connection is using SSL.  Thus, we
+  //     name mod_ssl.c as a predecessor.  This is redundant, since mod_ssl's
+  //     pre-connection hook uses APR_HOOK_MIDDLE, but it's good to be sure.
+  // For more about controlling hook order, see TAMB 10.2.2 or
+  // http://httpd.apache.org/docs/trunk/developer/hooks.html#hooking-order
+  ap_hook_pre_connection(
+      PreConnection,              // hook function to be called
+      modules_mod_ssl,            // predecessors
+      modules_core,               // successors
+      APR_HOOK_LAST);             // position
+
+  // Register our process-connection hook, which will handle SPDY connections.
+  // The first process-connection hook in the chain to return OK gets to be in
+  // charge of handling the connection from start to finish, so we put
+  // ourselves in APR_HOOK_FIRST so we can get an early look at the connection.
+  // If it turns out not to be a SPDY connection, we'll get out of the way and
+  // let other modules deal with it.
+  ap_hook_process_connection(ProcessConnection, NULL, NULL, APR_HOOK_FIRST);
+
+  // For the benefit of e.g. PHP/CGI scripts, we need to set various subprocess
+  // environment variables for each request served via SPDY.  Register a hook
+  // to do so; we use the fixup hook for this because that's the same hook that
+  // mod_ssl uses for setting its subprocess environment variables.
+  ap_hook_fixups(SetUpSubprocessEnv, NULL, NULL, APR_HOOK_MIDDLE);
+
+  // Our server push filter is a request-level filter, so we insert it with the
+  // insert-filter hook.
+  ap_hook_insert_filter(InsertRequestFilters, NULL, NULL, APR_HOOK_MIDDLE);
+
+  // Register a hook with mod_ssl to be called when deciding what protocols to
+  // advertise during Next Protocol Negotiatiation (NPN); we'll use this
+  // opportunity to advertise that we support SPDY.  This hook is declared in
+  // mod_ssl.h, for appropriately-patched versions of mod_ssl.  See TAMB 10.2.3
+  // for more about optional hooks.
+  APR_OPTIONAL_HOOK(
+      modssl,                     // prefix of optional hook
+      npn_advertise_protos_hook,  // name of optional hook
+      AdvertiseSpdy,              // hook function to be called
+      NULL,                       // predecessors
+      NULL,                       // successors
+      APR_HOOK_MIDDLE);           // position
+  // If we're advertising SPDY support via NPN, we ought to also advertise HTTP
+  // support.  Ideally, the Apache core HTTP module would do this, but for now
+  // it doesn't, so we'll do it for them.  We use APR_HOOK_LAST here, since
+  // http/1.1 is our last choice.  Note that our AdvertiseHttp function won't
+  // add "http/1.1" to the list if it's already there, so this is future-proof.
+  APR_OPTIONAL_HOOK(modssl, npn_advertise_protos_hook,
+                    AdvertiseHttp, NULL, NULL, APR_HOOK_LAST);
+
+  // Register a hook with mod_ssl to be called when NPN has been completed and
+  // the next protocol decided upon.  This hook will check if we're actually to
+  // be using SPDY with the client, and enable this module if so.  This hook is
+  // declared in mod_ssl.h, for appropriately-patched versions of mod_ssl.
+  APR_OPTIONAL_HOOK(
+      modssl,                     // prefix of optional hook
+      npn_proto_negotiated_hook,  // name of optional hook
+      OnNextProtocolNegotiated,   // hook function to be called
+      NULL,                       // predecessors
+      NULL,                       // successors
+      APR_HOOK_MIDDLE);           // position
+
+  // Create the various filters that will be used to route bytes to/from us
+  // on slave connections.
+  mod_spdy::ApacheSpdyStreamTaskFactory::InitFilters();
+
+  // Also create the filter we will use to detect us being instructed to
+  // do server pushes.
+  gServerPushFilterHandle = ap_register_output_filter(
+      "SPDY_SERVER_PUSH",         // name
+      ServerPushFilterFunc,       // filter function
+      NULL,                       // init function (n/a in our case)
+      // We use PROTOCOL-1 so that we come in just before the core HTTP_HEADER
+      // filter serializes the response header table.  That way we have a
+      // chance to remove the X-Associated-Content header before it is sent to
+      // the client, while still letting us run as late as possible so that we
+      // can catch headers set by a variety of modules (for example,
+      // mod_headers doesn't run until the CONTENT_SET stage, so if we ran at
+      // the RESOURCE stage, that would be too early).
+      static_cast<ap_filter_type>(AP_FTYPE_PROTOCOL - 1));
+
+  // Register our optional functions, so that other modules can retrieve and
+  // use them.  See TAMB 10.1.2.
+  APR_REGISTER_OPTIONAL_FN(spdy_get_version);
+  ModSpdyExportSlaveConnectionFunctions();
+}
+
+}  // namespace
+
+extern "C" {
+
+  // Export our module so Apache is able to load us.
+  // See http://gcc.gnu.org/wiki/Visibility for more information.
+#if defined(__linux)
+#pragma GCC visibility push(default)
+#endif
+
+  // Declare our module object (note that "module" is a typedef for "struct
+  // module_struct"; see http_config.h for the definition of module_struct).
+  module AP_MODULE_DECLARE_DATA spdy_module = {
+    // This next macro indicates that this is a (non-MPM) Apache 2.0 module
+    // (the macro actually expands to multiple comma-separated arguments; see
+    // http_config.h for the definition):
+    STANDARD20_MODULE_STUFF,
+
+    // These next four arguments are callbacks for manipulating configuration
+    // structures (the ones we don't need are left null):
+    NULL,  // create per-directory config structure
+    NULL,  // merge per-directory config structures
+    mod_spdy::CreateSpdyServerConfig,  // create per-server config structure
+    mod_spdy::MergeSpdyServerConfigs,  // merge per-server config structures
+
+    // This argument supplies a table describing the configuration directives
+    // implemented by this module:
+    mod_spdy::kSpdyConfigCommands,
+
+    // Finally, this function will be called to register hooks for this module:
+    RegisterHooks
+  };
+
+#if defined(__linux)
+#pragma GCC visibility pop
+#endif
+
+}  // extern "C"

Added: httpd/httpd/trunk/modules/spdy/mod_spdy.gyp
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/spdy/mod_spdy.gyp?rev=1590597&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/spdy/mod_spdy.gyp (added)
+++ httpd/httpd/trunk/modules/spdy/mod_spdy.gyp Mon Apr 28 10:55:17 2014
@@ -0,0 +1,199 @@
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+  'variables': {
+    # Turning on chromium_code mode enables extra compiler warnings.  See
+    # src/build/common.gypi.
+    'chromium_code': 1,
+  },
+  'targets': [
+    {
+      'target_name': 'spdy_common',
+      'type': '<(library)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+        '<(DEPTH)/net/net.gyp:instaweb_util',
+        '<(DEPTH)/net/net.gyp:spdy',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'export_dependent_settings': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/net/net.gyp:spdy',
+      ],
+      'sources': [
+        'common/executor.cc',
+        'common/http_request_visitor_interface.cc',
+        'common/http_response_parser.cc',
+        'common/http_response_visitor_interface.cc',
+        'common/http_string_builder.cc',
+        'common/http_to_spdy_converter.cc',
+        'common/protocol_util.cc',
+        'common/server_push_discovery_learner.cc',
+        'common/server_push_discovery_session.cc',
+        'common/shared_flow_control_window.cc',
+        'common/spdy_frame_priority_queue.cc',
+        'common/spdy_frame_queue.cc',
+        'common/spdy_server_config.cc',
+        'common/spdy_server_push_interface.cc',
+        'common/spdy_session.cc',
+        'common/spdy_session_io.cc',
+        'common/spdy_stream.cc',
+        'common/spdy_stream_task_factory.cc',
+        'common/spdy_to_http_converter.cc',
+        'common/thread_pool.cc',
+      ],
+    },
+    {
+      'target_name': 'spdy_apache',
+      'type': '<(library)',
+      'dependencies': [
+        'spdy_common',
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+        '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'export_dependent_settings': [
+        'spdy_common',
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+      ],
+      'sources': [
+        'apache/apache_spdy_session_io.cc',
+        'apache/apache_spdy_stream_task_factory.cc',
+        'apache/config_commands.cc',
+        'apache/config_util.cc',
+        'apache/filters/http_to_spdy_filter.cc',
+        'apache/filters/server_push_filter.cc',
+        'apache/filters/spdy_to_http_filter.cc',
+        'apache/id_pool.cc',
+        'apache/log_message_handler.cc',
+        'apache/master_connection_context.cc',
+        'apache/pool_util.cc',
+        'apache/sockaddr_util.cc',
+        'apache/slave_connection.cc',
+        'apache/slave_connection_api.cc',
+        'apache/slave_connection_context.cc',
+        'apache/ssl_util.cc',
+      ],
+    },
+    {
+      'target_name': 'mod_spdy',
+      'type': 'loadable_module',
+      'dependencies': [
+        'spdy_apache',
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+        '<(DEPTH)/net/net.gyp:spdy',
+        '<(DEPTH)/third_party/apache/httpd/httpd.gyp:include',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'sources': [
+        'mod_spdy.cc',
+      ],
+      'conditions': [['OS == "mac"', {
+        'xcode_settings': {
+          # We must null out these two variables when building this target,
+          # because it is a loadable_module (-bundle).
+          'DYLIB_COMPATIBILITY_VERSION':'',
+          'DYLIB_CURRENT_VERSION':'',
+        }
+      }]],
+    },
+    {
+      'target_name': 'spdy_common_testing',
+      'type': '<(library)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/net/net.gyp:instaweb_util',
+        '<(DEPTH)/net/net.gyp:spdy',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'export_dependent_settings': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/net/net.gyp:spdy',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+      ],
+      'sources': [
+        'common/testing/async_task_runner.cc',
+        'common/testing/notification.cc',
+        'common/testing/spdy_frame_matchers.cc',
+      ],
+    },
+    {
+      'target_name': 'spdy_common_test',
+      'type': 'executable',
+      'dependencies': [
+        'spdy_common',
+        'spdy_common_testing',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/testing/gtest.gyp:gtest_main',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'sources': [
+        'common/http_response_parser_test.cc',
+        'common/http_to_spdy_converter_test.cc',
+        'common/protocol_util_test.cc',
+        'common/server_push_discovery_learner_test.cc',
+        'common/server_push_discovery_session_test.cc',
+        'common/shared_flow_control_window_test.cc',
+        'common/spdy_frame_priority_queue_test.cc',
+        'common/spdy_frame_queue_test.cc',
+        'common/spdy_session_test.cc',
+        'common/spdy_stream_test.cc',
+        'common/spdy_to_http_converter_test.cc',
+        'common/thread_pool_test.cc',
+      ],
+    },
+    {
+      'target_name': 'spdy_apache_test',
+      'type': 'executable',
+      'dependencies': [
+        'spdy_apache',
+        'spdy_common_testing',
+        '<(DEPTH)/build/build_util.gyp:mod_spdy_version_header',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/third_party/apache/apr/apr.gyp:apr',
+        '<(DEPTH)/third_party/apache/aprutil/aprutil.gyp:aprutil',
+      ],
+      'include_dirs': [
+        '<(DEPTH)',
+      ],
+      'sources': [
+        'apache/filters/http_to_spdy_filter_test.cc',
+        'apache/filters/server_push_filter_test.cc',
+        'apache/filters/spdy_to_http_filter_test.cc',
+        'apache/id_pool_test.cc',
+        'apache/pool_util_test.cc',
+        'apache/sockaddr_util_test.cc',
+        'apache/testing/dummy_util_filter.cc',
+        'apache/testing/spdy_apache_test_main.cc',
+      ],
+    },
+  ],
+}



Mime
View raw message