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_