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.h b/hook_table.h
new file mode 100644
index 0000000..237f132
--- /dev/null
+++ b/hook_table.h
@@ -0,0 +1,109 @@
+// 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.
+
+#ifndef SHILL_HOOK_TABLE_H_
+#define SHILL_HOOK_TABLE_H_
+
+// 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 this facility use the Add() function to
+// provide a closure for starting an action and a callback for polling 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.
+//
+// Usage example.  Add an action to a hook table like this:
+//
+//   HookTable hook_table_(&event_dispatcher);
+//   Closure start_cb = Bind(&MyService::Disconnect, &my_service);
+//   Callback poll_cb = Bind(&MyService::IsConnected, &my_service);
+//   hook_table_.Add("MyService", start_cb, poll_cb);
+//
+// The code that catches an event runs the actions of the hook table like this:
+//
+//   Callback<void(const Error &)> done_cb =
+//     Bind(Manager::OnDisconnect, &manager);
+//   hook_table_.Run(kTimeout, done_cb);
+//
+// When |my_service| has completed its disconnect process,
+// Manager::OnDisconnect() gets called with Error::kSuccess.  If |my_service|
+// does not finish its disconnect processing before kTimeout, then it gets
+// called with kOperationTimeout.
+
+#include <map>
+#include <string>
+
+#include <base/basictypes.h>
+#include <base/callback.h>
+#include <gtest/gtest_prod.h>
+
+namespace shill {
+class Error;
+class EventDispatcher;
+
+class HookTable {
+ public:
+  explicit HookTable(EventDispatcher *event_dispatcher);
+
+  // Adds a closure to the hook table.  |name| should be unique; otherwise, a
+  // previous closure by the same name will NOT be replaced.  |start| will be
+  // called when Run() is called.  Run() will poll to see if the action has
+  // completed by calling |poll|, which should return true when the action has
+  // completed.
+  void Add(const std::string &name, const base::Closure &start,
+           const base::Callback<bool()> &poll);
+
+  // Runs the actions that have been added to the HookTable via Add().  It polls
+  // for completion up to |timeout_seconds|.  If all actions complete within the
+  // timeout period, |done| is called with a value of Error::kSuccess.
+  // Otherwise, it is called with Error::kOperationTimeout.
+  void Run(int timeout_seconds,
+           const base::Callback<void(const Error &)> &done);
+
+ private:
+  FRIEND_TEST(HookTableTest, ActionTimesOut);
+  FRIEND_TEST(HookTableTest, DelayedAction);
+  FRIEND_TEST(HookTableTest, MultipleActionsAllSucceed);
+  FRIEND_TEST(HookTableTest, MultipleActionsAndOneTimesOut);
+
+  // The |timeout_seconds| passed to Run() is divided into |kPollIterations|,
+  // polled once iteration.
+  static const int kPollIterations;
+
+  // For each action, there is a |start| and a |poll| callback, which are stored
+  // in this structure.
+  struct HookCallbacks {
+    HookCallbacks(const base::Closure &s, const base::Callback<bool()> &p)
+        : start(s), poll(p) {}
+    const base::Closure start;
+    const base::Callback<bool()> poll;
+  };
+
+  // Each action is stored in this table.  The key is |name| passed to Add().
+  typedef std::map<std::string, HookCallbacks> HookTableMap;
+
+  // Calls all the |poll| callbacks in |hook_table_|.  If all of them return
+  // true, then |done| is called with Error::kSuccess.  Otherwise, it queues
+  // itself in the |event_dispatcher_| to be called again later.  It repeats
+  // this process up to |kPollIterations|, after which if an action still has
+  // not completed, |done| is called with Error::kOperationTimeout.
+  void PollActions(int timeout_seconds,
+                   const base::Callback<void(const Error &)> &done);
+
+  // Each action is stored in this table.
+  HookTableMap hook_table_;
+
+  // Used for polling actions that do not complete immediately.
+  EventDispatcher *const event_dispatcher_;
+
+  // Counts the number of polling attempts.
+  int iteration_counter_;
+
+  DISALLOW_COPY_AND_ASSIGN(HookTable);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_HOOK_TABLE_H_