shill: HookTable collects async actions to be executed later

HookTable provides a facility for starting a set of generic
actions and polling for their completion.  For example, on shutdown,
each service gets disconnected.  A disconnect action may be
instantaneous or it may require some time to complete.  Users of
HookTable use the Add() function to provide a closure for starting
an action and a callback for pollings its completion.  When an event
occurs, the Run() function is called, which starts each action and
polls for completion.  Upon completion or timeout, Run() calls a
user-supplied callback to notify the caller of the state of actions.

BUG=chromium-os:22408
TEST=new unittests; ran all unittests.

Change-Id: Ia42816a2a2b3c252108665ec64bc3c68f886463b
Reviewed-on: https://gerrit.chromium.org/gerrit/22862
Commit-Ready: Gary Morain <gmorain@chromium.org>
Reviewed-by: Gary Morain <gmorain@chromium.org>
Tested-by: Gary Morain <gmorain@chromium.org>
diff --git a/hook_table.cc b/hook_table.cc
new file mode 100644
index 0000000..53dbc29
--- /dev/null
+++ b/hook_table.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/hook_table.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/callback.h>
+
+#include "shill/error.h"
+#include "shill/event_dispatcher.h"
+
+using base::Bind;
+using base::Closure;
+using base::ConstRef;
+using base::Unretained;
+using std::string;
+
+namespace shill {
+
+const int HookTable::kPollIterations = 10;
+
+HookTable::HookTable(EventDispatcher *event_dispatcher)
+    : event_dispatcher_(event_dispatcher),
+      iteration_counter_(0) {}
+
+void HookTable::Add(const string &name, const base::Closure &start,
+                    const base::Callback<bool()> &poll) {
+  hook_table_.insert(
+      HookTableMap::value_type(name, HookCallbacks(start, poll)));
+}
+
+void HookTable::Run(int timeout_seconds,
+                    const base::Callback<void(const Error &)> &done) {
+  // Run all the start actions in the hook table.
+  for (HookTableMap::const_iterator it = hook_table_.begin();
+       it != hook_table_.end(); ++it) {
+    it->second.start.Run();
+  }
+
+  // Start polling for completion.
+  iteration_counter_ = 0;
+  PollActions(timeout_seconds, done);
+}
+
+void HookTable::PollActions(int timeout_seconds,
+                            const base::Callback<void(const Error &)> &done) {
+  // Call all the poll functions in the hook table.
+  bool all_done = true;
+  for (HookTableMap::const_iterator it = hook_table_.begin();
+       it != hook_table_.end(); ++it) {
+    if (!it->second.poll.Run()) {
+      all_done = false;
+    }
+  }
+
+  if (all_done) {
+    done.Run(Error(Error::kSuccess));
+    return;
+  }
+
+  if (iteration_counter_ >= kPollIterations) {
+    done.Run(Error(Error::kOperationTimeout));
+    return;
+  }
+
+  // Some actions have not yet completed.  Queue this function to poll again
+  // later.
+  Closure poll_actions_cb = Bind(&HookTable::PollActions, Unretained(this),
+                                 timeout_seconds, ConstRef(done));
+  const uint64 delay_ms = (timeout_seconds * 1000) / kPollIterations;
+  event_dispatcher_->PostDelayedTask(poll_actions_cb, delay_ms);
+  iteration_counter_++;
+}
+
+}  // namespace shill