buffet: Add a helper to coordinate async callbacks

We frequently end up in situations where we want to call an "init
succeeded" callback after many other callbacks indicating partial
init success come back.  AsyncEventCoordinator will manage this
process for us in a general way.

BUG=chromium:360831
TEST=Unittests

CQ-DEPEND=CL:193650

Change-Id: I968a65e88d60199d0355743a2fcee7e20156bc31
Reviewed-on: https://chromium-review.googlesource.com/194362
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/async_event_sequencer.cc b/buffet/async_event_sequencer.cc
new file mode 100644
index 0000000..b447c70
--- /dev/null
+++ b/buffet/async_event_sequencer.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 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 "buffet/async_event_sequencer.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+AsyncEventSequencer::AsyncEventSequencer() { }
+AsyncEventSequencer::~AsyncEventSequencer() { }
+
+AsyncEventSequencer::Handler AsyncEventSequencer::GetHandler(
+    const std::string& descriptive_message, bool failure_is_fatal) {
+  CHECK(!started_) << "Cannot create handlers after OnAllTasksCompletedCall()";
+  int unique_registration_id = ++registration_counter_;
+  outstanding_registrations_.insert(unique_registration_id);
+  return base::Bind(&AsyncEventSequencer::HandleFinish, this,
+                    unique_registration_id, descriptive_message,
+                    failure_is_fatal);
+}
+
+AsyncEventSequencer::ExportHandler AsyncEventSequencer::GetExportHandler(
+        const std::string& interface_name, const std::string& method_name,
+        const std::string& descriptive_message, bool failure_is_fatal) {
+  auto finish_handler = GetHandler(descriptive_message, failure_is_fatal);
+  return base::Bind(&AsyncEventSequencer::HandleDBusMethodExported, this,
+                    finish_handler,
+                    interface_name,
+                    method_name);
+}
+
+void AsyncEventSequencer::OnAllTasksCompletedCall(
+    std::vector<CompletionAction> actions) {
+  CHECK(!started_) << "OnAllTasksCompletedCall called twice!";
+  started_ = true;
+  completion_actions_.assign(actions.begin(), actions.end());
+  // All of our callbacks might have been called already.
+  PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleFinish(int registration_number,
+                                       const std::string& error_message,
+                                       bool failure_is_fatal, bool success) {
+  RetireRegistration(registration_number);
+  CheckForFailure(failure_is_fatal, success, error_message);
+  PossiblyRunCompletionActions();
+}
+
+void AsyncEventSequencer::HandleDBusMethodExported(
+    const AsyncEventSequencer::Handler& finish_handler,
+    const std::string& expected_interface_name,
+    const std::string& expected_method_name,
+    const std::string& actual_interface_name,
+    const std::string& actual_method_name, bool success) {
+  CHECK(expected_method_name == actual_method_name)
+      << "Exported DBus method '" << actual_method_name << "' "
+      << "but expected '" << expected_method_name << "'";
+  CHECK(expected_interface_name == actual_interface_name)
+      << "Exported method DBus interface '" << actual_interface_name << "' "
+      << "but expected '" << expected_interface_name << "'";
+  finish_handler.Run(success);
+}
+
+
+void AsyncEventSequencer::RetireRegistration(int registration_number) {
+  const size_t handlers_retired = outstanding_registrations_.erase(
+      registration_number);
+  CHECK(handlers_retired == 1)
+      << "Tried to retire invalid handler " << registration_number << ")";
+}
+
+void AsyncEventSequencer::CheckForFailure(bool failure_is_fatal, bool success,
+                                          const std::string& error_message) {
+  if (failure_is_fatal) {
+    CHECK(success) << error_message;
+  }
+  if (!success) {
+    LOG(ERROR) << error_message;
+    had_failures_ = true;
+  }
+}
+
+void AsyncEventSequencer::PossiblyRunCompletionActions() {
+  if (!started_ || !outstanding_registrations_.empty()) {
+    // Don't run completion actions if we have any outstanding
+    // Handlers outstanding or if any more handlers might
+    // be scheduled in the future.
+    return;
+  }
+  for (auto&& completion_action : completion_actions_) {
+    // Should this be put on the message loop or run directly?
+    completion_action.Run(!had_failures_);
+  }
+  // Discard our references to those actions.
+  completion_actions_.clear();
+}
+
+}  // namespace dbus_utils
+
+}  // namespace buffet