Allow to Suspend/Resume the ActionProcessor.
This patch implements the core functionality of suspend/resume actions
from the ActionProcessor. No actions support suspend/resume yet.
Bug: 27047026
TEST=Added unittets, tested on edison-eng.
Change-Id: Ib9600098dbccf05fc30f10f0add4a5bc87892b66
diff --git a/common/action.h b/common/action.h
index d8049ac..6c88216 100644
--- a/common/action.h
+++ b/common/action.h
@@ -124,6 +124,15 @@
// Only the ActionProcessor should call this.
virtual void TerminateProcessing() {}
+ // Called on asynchronous actions if the processing is suspended and resumed,
+ // respectively. These methods are called by the ActionProcessor and should
+ // not be explicitly called.
+ // The action may still call ActionCompleted() once the action is completed
+ // while the processing is suspended, for example if suspend/resume is not
+ // implemented for the given action.
+ virtual void SuspendAction() {}
+ virtual void ResumeAction() {}
+
// These methods are useful for debugging. TODO(adlr): consider using
// std::type_info for this?
// Type() returns a string of the Action type. I.e., for DownloadAction,
diff --git a/common/action_processor.cc b/common/action_processor.cc
index 7ccdfbd..2618e4e 100644
--- a/common/action_processor.cc
+++ b/common/action_processor.cc
@@ -21,14 +21,12 @@
#include <base/logging.h>
#include "update_engine/common/action.h"
+#include "update_engine/common/error_code_utils.h"
using std::string;
namespace chromeos_update_engine {
-ActionProcessor::ActionProcessor()
- : current_action_(nullptr), delegate_(nullptr) {}
-
ActionProcessor::~ActionProcessor() {
if (IsRunning())
StopProcessing();
@@ -45,8 +43,7 @@
CHECK(!IsRunning());
if (!actions_.empty()) {
current_action_ = actions_.front();
- LOG(INFO) << "ActionProcessor::StartProcessing: "
- << current_action_->Type();
+ LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
actions_.pop_front();
current_action_->PerformAction();
}
@@ -54,17 +51,55 @@
void ActionProcessor::StopProcessing() {
CHECK(IsRunning());
- CHECK(current_action_);
- current_action_->TerminateProcessing();
- CHECK(current_action_);
- current_action_->SetProcessor(nullptr);
- LOG(INFO) << "ActionProcessor::StopProcessing: aborted "
- << current_action_->Type();
+ if (current_action_) {
+ current_action_->TerminateProcessing();
+ current_action_->SetProcessor(nullptr);
+ }
+ LOG(INFO) << "ActionProcessor: aborted "
+ << (current_action_ ? current_action_->Type() : "")
+ << (suspended_ ? " while suspended" : "");
current_action_ = nullptr;
+ suspended_ = false;
if (delegate_)
delegate_->ProcessingStopped(this);
}
+void ActionProcessor::SuspendProcessing() {
+ // No current_action_ when not suspended means that the action processor was
+ // never started or already finished.
+ if (suspended_ || !current_action_) {
+ LOG(WARNING) << "Called SuspendProcessing while not processing.";
+ return;
+ }
+ suspended_ = true;
+
+ // If there's a current action we should notify it that it should suspend, but
+ // the action can ignore that and terminate at any point.
+ LOG(INFO) << "ActionProcessor: suspending " << current_action_->Type();
+ current_action_->SuspendAction();
+}
+
+void ActionProcessor::ResumeProcessing() {
+ if (!suspended_) {
+ LOG(WARNING) << "Called ResumeProcessing while not suspended.";
+ return;
+ }
+ suspended_ = false;
+ if (current_action_) {
+ // The current_action_ did not call ActionComplete while suspended, so we
+ // should notify it of the resume operation.
+ LOG(INFO) << "ActionProcessor: resuming " << current_action_->Type();
+ current_action_->ResumeAction();
+ } else {
+ // The last action called ActionComplete while suspended, so there is
+ // already a log message with the type of the finished action. We simply
+ // state that we are resuming processing and the next function will log the
+ // start of the next action or processing completion.
+ LOG(INFO) << "ActionProcessor: resuming processing";
+ StartNextActionOrFinish(suspended_error_code_);
+ }
+}
+
void ActionProcessor::ActionComplete(AbstractAction* actionptr,
ErrorCode code) {
CHECK_EQ(actionptr, current_action_);
@@ -74,17 +109,26 @@
current_action_->ActionCompleted(code);
current_action_->SetProcessor(nullptr);
current_action_ = nullptr;
- if (actions_.empty()) {
- LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
- " type " << old_type;
- } else if (code != ErrorCode::kSuccess) {
- LOG(INFO) << "ActionProcessor::ActionComplete: " << old_type
- << " action failed. Aborting processing.";
+ LOG(INFO) << "ActionProcessor: finished "
+ << (actions_.empty() ? "last action " : "") << old_type
+ << (suspended_ ? " while suspended" : "")
+ << " with code " << utils::ErrorCodeToString(code);
+ if (!actions_.empty() && code != ErrorCode::kSuccess) {
+ LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
actions_.clear();
}
+ if (suspended_) {
+ // If an action finished while suspended we don't start the next action (or
+ // terminate the processing) until the processor is resumed. This condition
+ // will be flagged by a nullptr current_action_ while suspended_ is true.
+ suspended_error_code_ = code;
+ return;
+ }
+ StartNextActionOrFinish(code);
+}
+
+void ActionProcessor::StartNextActionOrFinish(ErrorCode code) {
if (actions_.empty()) {
- LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
- " type " << old_type;
if (delegate_) {
delegate_->ProcessingDone(this, code);
}
@@ -92,8 +136,7 @@
}
current_action_ = actions_.front();
actions_.pop_front();
- LOG(INFO) << "ActionProcessor::ActionComplete: finished " << old_type
- << ", starting " << current_action_->Type();
+ LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
current_action_->PerformAction();
}
diff --git a/common/action_processor.h b/common/action_processor.h
index d61e12d..050eee9 100644
--- a/common/action_processor.h
+++ b/common/action_processor.h
@@ -20,6 +20,7 @@
#include <deque>
#include <base/macros.h>
+#include <brillo/errors/error.h>
#include "update_engine/common/error_code.h"
@@ -40,7 +41,7 @@
class ActionProcessor {
public:
- ActionProcessor();
+ ActionProcessor() = default;
virtual ~ActionProcessor();
@@ -54,8 +55,20 @@
// will be lost and must be re-enqueued if this Processor is to use it.
void StopProcessing();
- // Returns true iff an Action is currently processing.
- bool IsRunning() const { return nullptr != current_action_; }
+ // Suspend the processing. If an Action is running, it will have the
+ // SuspendProcessing() called on it, and it should suspend operations until
+ // ResumeProcessing() is called on this class to continue. While suspended,
+ // no new actions will be started. Calling SuspendProcessing while the
+ // processing is suspended or not running this method performs no action.
+ void SuspendProcessing();
+
+ // Resume the suspended processing. If the ActionProcessor is not suspended
+ // or not running on the first place this method performs no action.
+ void ResumeProcessing();
+
+ // Returns true iff the processing was started but not yet completed nor
+ // stopped.
+ bool IsRunning() const { return current_action_ != nullptr || suspended_; }
// Adds another Action to the end of the queue.
virtual void EnqueueAction(AbstractAction* action);
@@ -75,15 +88,29 @@
void ActionComplete(AbstractAction* actionptr, ErrorCode code);
private:
+ // Continue processing actions (if any) after the last action terminated with
+ // the passed error code. If there are no more actions to process, the
+ // processing will terminate.
+ void StartNextActionOrFinish(ErrorCode code);
+
// Actions that have not yet begun processing, in the order in which
// they'll be processed.
std::deque<AbstractAction*> actions_;
// A pointer to the currently processing Action, if any.
- AbstractAction* current_action_;
+ AbstractAction* current_action_{nullptr};
+
+ // The ErrorCode reported by an action that was suspended but finished while
+ // being suspended. This error code is stored here to be reported back to the
+ // delegate once the processor is resumed.
+ ErrorCode suspended_error_code_{ErrorCode::kSuccess};
+
+ // Whether the action processor is or should be suspended.
+ bool suspended_{false};
// A pointer to the delegate, or null if none.
- ActionProcessorDelegate *delegate_;
+ ActionProcessorDelegate* delegate_{nullptr};
+
DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
};
diff --git a/common/action_processor_unittest.cc b/common/action_processor_unittest.cc
index 8285470..631e42d 100644
--- a/common/action_processor_unittest.cc
+++ b/common/action_processor_unittest.cc
@@ -16,9 +16,12 @@
#include "update_engine/common/action_processor.h"
-#include <gtest/gtest.h>
#include <string>
+
+#include <gtest/gtest.h>
+
#include "update_engine/common/action.h"
+#include "update_engine/common/mock_action.h"
using std::string;
@@ -51,26 +54,6 @@
string Type() const { return "ActionProcessorTestAction"; }
};
-class ActionProcessorTest : public ::testing::Test { };
-
-// This test creates two simple Actions and sends a message via an ActionPipe
-// from one to the other.
-TEST(ActionProcessorTest, SimpleTest) {
- ActionProcessorTestAction action;
- ActionProcessor action_processor;
- EXPECT_FALSE(action_processor.IsRunning());
- action_processor.EnqueueAction(&action);
- EXPECT_FALSE(action_processor.IsRunning());
- EXPECT_FALSE(action.IsRunning());
- action_processor.StartProcessing();
- EXPECT_TRUE(action_processor.IsRunning());
- EXPECT_TRUE(action.IsRunning());
- EXPECT_EQ(action_processor.current_action(), &action);
- action.CompleteAction();
- EXPECT_FALSE(action_processor.IsRunning());
- EXPECT_FALSE(action.IsRunning());
-}
-
namespace {
class MyActionProcessorDelegate : public ActionProcessorDelegate {
public:
@@ -109,53 +92,79 @@
};
} // namespace
-TEST(ActionProcessorTest, DelegateTest) {
- ActionProcessorTestAction action;
- ActionProcessor action_processor;
- MyActionProcessorDelegate delegate(&action_processor);
- action_processor.set_delegate(&delegate);
+class ActionProcessorTest : public ::testing::Test {
+ void SetUp() override {
+ action_processor_.set_delegate(&delegate_);
+ // Silence Type() calls used for logging.
+ EXPECT_CALL(mock_action_, Type()).Times(testing::AnyNumber());
+ }
- action_processor.EnqueueAction(&action);
- action_processor.StartProcessing();
- action.CompleteAction();
- action_processor.set_delegate(nullptr);
- EXPECT_TRUE(delegate.processing_done_called_);
- EXPECT_TRUE(delegate.action_completed_called_);
+ void TearDown() override {
+ action_processor_.set_delegate(nullptr);
+ }
+
+ protected:
+ // The ActionProcessor under test.
+ ActionProcessor action_processor_;
+
+ MyActionProcessorDelegate delegate_{&action_processor_};
+
+ // Common actions used during most tests.
+ testing::StrictMock<MockAction> mock_action_;
+ ActionProcessorTestAction action_;
+};
+
+TEST_F(ActionProcessorTest, SimpleTest) {
+ EXPECT_FALSE(action_processor_.IsRunning());
+ action_processor_.EnqueueAction(&action_);
+ EXPECT_FALSE(action_processor_.IsRunning());
+ EXPECT_FALSE(action_.IsRunning());
+ action_processor_.StartProcessing();
+ EXPECT_TRUE(action_processor_.IsRunning());
+ EXPECT_TRUE(action_.IsRunning());
+ EXPECT_EQ(action_processor_.current_action(), &action_);
+ action_.CompleteAction();
+ EXPECT_FALSE(action_processor_.IsRunning());
+ EXPECT_FALSE(action_.IsRunning());
}
-TEST(ActionProcessorTest, StopProcessingTest) {
- ActionProcessorTestAction action;
- ActionProcessor action_processor;
- MyActionProcessorDelegate delegate(&action_processor);
- action_processor.set_delegate(&delegate);
-
- action_processor.EnqueueAction(&action);
- action_processor.StartProcessing();
- action_processor.StopProcessing();
- action_processor.set_delegate(nullptr);
- EXPECT_TRUE(delegate.processing_stopped_called_);
- EXPECT_FALSE(delegate.action_completed_called_);
- EXPECT_FALSE(action_processor.IsRunning());
- EXPECT_EQ(nullptr, action_processor.current_action());
+TEST_F(ActionProcessorTest, DelegateTest) {
+ action_processor_.EnqueueAction(&action_);
+ action_processor_.StartProcessing();
+ action_.CompleteAction();
+ EXPECT_TRUE(delegate_.processing_done_called_);
+ EXPECT_TRUE(delegate_.action_completed_called_);
}
-TEST(ActionProcessorTest, ChainActionsTest) {
+TEST_F(ActionProcessorTest, StopProcessingTest) {
+ action_processor_.EnqueueAction(&action_);
+ action_processor_.StartProcessing();
+ action_processor_.StopProcessing();
+ EXPECT_TRUE(delegate_.processing_stopped_called_);
+ EXPECT_FALSE(delegate_.action_completed_called_);
+ EXPECT_FALSE(action_processor_.IsRunning());
+ EXPECT_EQ(nullptr, action_processor_.current_action());
+}
+
+TEST_F(ActionProcessorTest, ChainActionsTest) {
+ // This test doesn't use a delegate since it terminates several actions.
+ action_processor_.set_delegate(nullptr);
+
ActionProcessorTestAction action1, action2;
- ActionProcessor action_processor;
- action_processor.EnqueueAction(&action1);
- action_processor.EnqueueAction(&action2);
- action_processor.StartProcessing();
- EXPECT_EQ(&action1, action_processor.current_action());
- EXPECT_TRUE(action_processor.IsRunning());
+ action_processor_.EnqueueAction(&action1);
+ action_processor_.EnqueueAction(&action2);
+ action_processor_.StartProcessing();
+ EXPECT_EQ(&action1, action_processor_.current_action());
+ EXPECT_TRUE(action_processor_.IsRunning());
action1.CompleteAction();
- EXPECT_EQ(&action2, action_processor.current_action());
- EXPECT_TRUE(action_processor.IsRunning());
+ EXPECT_EQ(&action2, action_processor_.current_action());
+ EXPECT_TRUE(action_processor_.IsRunning());
action2.CompleteAction();
- EXPECT_EQ(nullptr, action_processor.current_action());
- EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_EQ(nullptr, action_processor_.current_action());
+ EXPECT_FALSE(action_processor_.IsRunning());
}
-TEST(ActionProcessorTest, DtorTest) {
+TEST_F(ActionProcessorTest, DtorTest) {
ActionProcessorTestAction action1, action2;
{
ActionProcessor action_processor;
@@ -169,22 +178,87 @@
EXPECT_FALSE(action2.IsRunning());
}
-TEST(ActionProcessorTest, DefaultDelegateTest) {
+TEST_F(ActionProcessorTest, DefaultDelegateTest) {
// Just make sure it doesn't crash
- ActionProcessorTestAction action;
- ActionProcessor action_processor;
- ActionProcessorDelegate delegate;
- action_processor.set_delegate(&delegate);
+ action_processor_.EnqueueAction(&action_);
+ action_processor_.StartProcessing();
+ action_.CompleteAction();
- action_processor.EnqueueAction(&action);
- action_processor.StartProcessing();
- action.CompleteAction();
+ action_processor_.EnqueueAction(&action_);
+ action_processor_.StartProcessing();
+ action_processor_.StopProcessing();
+}
- action_processor.EnqueueAction(&action);
- action_processor.StartProcessing();
- action_processor.StopProcessing();
+// This test suspends and resume the action processor while running one action_.
+TEST_F(ActionProcessorTest, SuspendResumeTest) {
+ action_processor_.EnqueueAction(&mock_action_);
- action_processor.set_delegate(nullptr);
+ testing::InSequence s;
+ EXPECT_CALL(mock_action_, PerformAction());
+ action_processor_.StartProcessing();
+
+ EXPECT_CALL(mock_action_, SuspendAction());
+ action_processor_.SuspendProcessing();
+ // Suspending the processor twice should not suspend the action twice.
+ action_processor_.SuspendProcessing();
+
+ // IsRunning should return whether there's is an action doing some work, even
+ // if it is suspended.
+ EXPECT_TRUE(action_processor_.IsRunning());
+ EXPECT_EQ(&mock_action_, action_processor_.current_action());
+
+ EXPECT_CALL(mock_action_, ResumeAction());
+ action_processor_.ResumeProcessing();
+
+ // Calling ResumeProcessing twice should not affect the action_.
+ action_processor_.ResumeProcessing();
+
+ action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+}
+
+// This test suspends an action that presumably doesn't support suspend/resume
+// and it finished before being resumed.
+TEST_F(ActionProcessorTest, ActionCompletedWhileSuspendedTest) {
+ action_processor_.EnqueueAction(&mock_action_);
+
+ testing::InSequence s;
+ EXPECT_CALL(mock_action_, PerformAction());
+ action_processor_.StartProcessing();
+
+ EXPECT_CALL(mock_action_, SuspendAction());
+ action_processor_.SuspendProcessing();
+
+ // Simulate the action completion while suspended. No other call to
+ // |mock_action_| is expected at this point.
+ action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+
+ // The processing should not be done since the ActionProcessor is suspended
+ // and the processing is considered to be still running until resumed.
+ EXPECT_FALSE(delegate_.processing_done_called_);
+ EXPECT_TRUE(action_processor_.IsRunning());
+
+ action_processor_.ResumeProcessing();
+ EXPECT_TRUE(delegate_.processing_done_called_);
+ EXPECT_FALSE(delegate_.processing_stopped_called_);
+}
+
+TEST_F(ActionProcessorTest, StoppedWhileSuspendedTest) {
+ action_processor_.EnqueueAction(&mock_action_);
+
+ testing::InSequence s;
+ EXPECT_CALL(mock_action_, PerformAction());
+ action_processor_.StartProcessing();
+ EXPECT_CALL(mock_action_, SuspendAction());
+ action_processor_.SuspendProcessing();
+
+ EXPECT_CALL(mock_action_, TerminateProcessing());
+ action_processor_.StopProcessing();
+ // Stopping the processing should abort the current execution no matter what.
+ EXPECT_TRUE(delegate_.processing_stopped_called_);
+ EXPECT_FALSE(delegate_.processing_done_called_);
+ EXPECT_FALSE(delegate_.action_completed_called_);
+ EXPECT_FALSE(action_processor_.IsRunning());
+ EXPECT_EQ(nullptr, action_processor_.current_action());
}
} // namespace chromeos_update_engine
diff --git a/common/mock_action.h b/common/mock_action.h
new file mode 100644
index 0000000..06acad1
--- /dev/null
+++ b/common/mock_action.h
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// 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 UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
+#define UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockAction;
+
+template <>
+class ActionTraits<MockAction> {
+ public:
+ typedef NoneType OutputObjectType;
+ typedef NoneType InputObjectType;
+};
+
+class MockAction : public Action<MockAction> {
+ public:
+ MockAction() {
+ ON_CALL(*this, Type()).WillByDefault(testing::Return("MockAction"));
+ }
+
+ MOCK_METHOD0(PerformAction, void());
+ MOCK_METHOD0(TerminateProcessing, void());
+ MOCK_METHOD0(SuspendAction, void());
+ MOCK_METHOD0(ResumeAction, void());
+ MOCK_CONST_METHOD0(Type, std::string());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
diff --git a/common/mock_action_processor.h b/common/mock_action_processor.h
new file mode 100644
index 0000000..04275c1
--- /dev/null
+++ b/common/mock_action_processor.h
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// 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 UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockActionProcessor : public ActionProcessor {
+ public:
+ MOCK_METHOD0(StartProcessing, void());
+ MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_