Merge pull request #304 from BenchR267/feature/39798231-add-pid-to-extended-result

Added process identifier to command_extended_result
diff --git a/lib/Core/BuildEngine.cpp b/lib/Core/BuildEngine.cpp
index 8312c89..b1efc1c 100644
--- a/lib/Core/BuildEngine.cpp
+++ b/lib/Core/BuildEngine.cpp
@@ -492,6 +492,7 @@
 
     // Create the task for this rule.
     Task* task = ruleInfo.rule.action(buildEngine);
+    assert(task && "rule action returned null task");
 
     // Find the task info for this task.
     auto taskInfo = getTaskInfo(task);
@@ -1237,7 +1238,9 @@
           finishedTaskInfos.clear();
         }
     }
-    
+
+    std::lock_guard<std::mutex> guard(taskInfosMutex);
+
     for (auto& it: taskInfos) {
       // Cancel the task, marking it incomplete.
       //
@@ -1350,10 +1353,12 @@
   RuleInfo& addRule(KeyID keyID, Rule&& rule) {
     auto result = ruleInfos.emplace(keyID, RuleInfo(keyID, std::move(rule)));
     if (!result.second) {
-      // FIXME: Error handling.
-      std::cerr << "error: attempt to register duplicate rule \""
-                << rule.key << "\"\n";
-      exit(1);
+      delegate.error("attempt to register duplicate rule \"" + rule.key + "\"\n");
+
+      // Set cancelled, but return something 'valid' for use until it is
+      // processed.
+      buildCancelled = true;
+      return result.first->second;
     }
 
     // If we have a database attached, retrieve any stored result.
@@ -1366,8 +1371,10 @@
       std::string error;
       db->lookupRuleResult(ruleInfo.keyID, ruleInfo.rule, &ruleInfo.result, &error);
       if (!error.empty()) {
+        // FIXME: Investigate changing the database error handling model to
+        // allow builds to proceed without the database.
         delegate.error(error);
-        cancelRemainingTasks();
+        buildCancelled = true;
       }
     }
 
@@ -1481,7 +1488,7 @@
   void dumpGraphToFile(const std::string& path) {
     FILE* fp = ::fopen(path.c_str(), "w");
     if (!fp) {
-      delegate.error("error: unable to open graph output path \"" + path + "\"");
+      delegate.error("unable to open graph output path \"" + path + "\"");
       return;
     }
 
@@ -1551,8 +1558,8 @@
   void taskNeedsInput(Task* task, const KeyType& key, uintptr_t inputID) {
     // Validate the InputID.
     if (inputID > BuildEngine::kMaximumInputID) {
-      delegate.error("error: attempt to use reserved input ID");
-      cancelRemainingTasks();
+      delegate.error("attempt to use reserved input ID");
+      buildCancelled = true;
       return;
     }
 
@@ -1569,8 +1576,8 @@
     assert(taskInfo && "cannot request inputs for an unknown task");
 
     if (!taskInfo->forRuleInfo->isInProgressComputing()) {
-      delegate.error("error: invalid state for adding discovered dependency");
-      cancelRemainingTasks();
+      delegate.error("invalid state for adding discovered dependency");
+      buildCancelled = true;
       return;
     }
 
@@ -1586,8 +1593,8 @@
     assert(taskInfo && "cannot request inputs for an unknown task");
 
     if (!taskInfo->forRuleInfo->isInProgressComputing()) {
-      delegate.error("error: invalid state for marking task complete");
-      cancelRemainingTasks();
+      delegate.error("invalid state for marking task complete");
+      buildCancelled = true;
       return;
     }
 
diff --git a/unittests/Core/BuildEngineCancellationTest.cpp b/unittests/Core/BuildEngineCancellationTest.cpp
index 8d25910..04b69e1 100644
--- a/unittests/Core/BuildEngineCancellationTest.cpp
+++ b/unittests/Core/BuildEngineCancellationTest.cpp
@@ -28,6 +28,8 @@
 namespace {
 
 class SimpleBuildEngineDelegate : public core::BuildEngineDelegate {
+public:
+  bool expectError = false;
 private:
   virtual core::Rule lookupRule(const core::KeyType& key) override {
     // We never expect dynamic rule lookup.
@@ -43,7 +45,8 @@
 
   virtual void error(const Twine& message) override {
     fprintf(stderr, "error: %s\n", message.str().c_str());
-    abort();
+    if (!expectError)
+      abort();
   }
 };
 
@@ -114,6 +117,13 @@
                                               compute)); };
 }
 
+static ActionFn simpleActionExternalTask(const std::vector<KeyType>& inputs,
+                             SimpleTask::ComputeFnType compute, Task** task) {
+  return [=] (BuildEngine& engine) {
+    *task = new SimpleTask([inputs]{ return inputs; }, compute);
+    return engine.registerTask(*task); };
+}
+
 TEST(BuildEngineCancellationTest, basic) {
   std::vector<std::string> builtKeys;
   SimpleBuildEngineDelegate delegate;
@@ -152,4 +162,50 @@
   EXPECT_EQ("result", builtKeys[1]);
 }
 
+TEST(BuildEngineCancellationDueToDuplicateTaskTest, basic) {
+  std::vector<std::string> builtKeys;
+  SimpleBuildEngineDelegate delegate;
+  core::BuildEngine engine(delegate);
+  Task* taskA = nullptr;
+  engine.addRule({
+     "value-A", simpleActionExternalTask({"value-B"},
+        [&] (const std::vector<int>& inputs) {
+          builtKeys.push_back("value-A");
+          fprintf(stderr, "building A\n");
+          return 2;
+        },
+        &taskA)
+    });
+  engine.addRule({
+     "value-B", simpleAction({}, [&] (const std::vector<int>& inputs) {
+       builtKeys.push_back("value-B");
+       fprintf(stderr, "building B (and reporting A complete early)\n");
+       engine.taskIsComplete(taskA, intToValue(2));
+       return 3; }) });
+  engine.addRule({
+      "result",
+      simpleAction({"value-A", "value-B"},
+                   [&] (const std::vector<int>& inputs) {
+                     EXPECT_EQ(2U, inputs.size());
+                     EXPECT_EQ(2, inputs[0]);
+                     EXPECT_EQ(3, inputs[1]);
+                     builtKeys.push_back("result");
+                     return inputs[0] * 3 + inputs[1];
+                   }) });
+
+  // Build the result, expecting error
+  //
+  // This test is triggering the cancellation behavior through what is
+  // clearly broken client behavior (reporting a task complete that should
+  // not have started yet). The engine should cleanly cancel and report the
+  // error, which is what this test is checking. However, the question remains
+  // are there 'legitimate'/non-broken client pathways that could also trigger
+  // that error?
+  delegate.expectError = true;
+  auto result = engine.build("result");
+  EXPECT_EQ(0U, result.size());
+  EXPECT_EQ(1U, builtKeys.size());
+  EXPECT_EQ("value-B", builtKeys[0]);
+}
+
 }