Mark nodes to be force-rebuilt during node restarting

Non-deterministic nodes may evaluate to different values after being
restarted. By ensuring that the nodes are "force-rebuilt" after a
restart, such a situation won't result in a crash.

Because restarted nodes are no longer dirtied with isChanged=true, the
assertion logic that ensures that nodes are not dirtied twice with the
same value for isChanged can go back into the #markDirty method. In
other words, this CL includes a logical rollback of "Permit marking
dirty/changed a node more than once".

This also fixes a race condition bug where a dep which is restarted by
>1 parent could cause a crash if the dep was inflight when the later
restart attempted to dirty it.

RELNOTES: None.
PiperOrigin-RevId: 214954192
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
index 2a0f664..25b867f 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
@@ -475,27 +475,35 @@
   }
 
   @Override
-  public synchronized MarkedDirtyResult markDirty(boolean isChanged) {
+  public synchronized MarkedDirtyResult markDirty(DirtyType dirtyType) {
     // Can't process a dirty node without its deps.
     assertKeepDeps();
     if (isDone()) {
       dirtyBuildingState =
-          DirtyBuildingState.create(isChanged, GroupedList.<SkyKey>create(directDeps), value);
+          DirtyBuildingState.create(dirtyType, GroupedList.create(directDeps), value);
       value = null;
       directDeps = null;
-      return new FromCleanMarkedDirtyResult(ReverseDepsUtility.getReverseDeps(this));
+      return new MarkedDirtyResult(ReverseDepsUtility.getReverseDeps(this));
     }
-
+    if (dirtyType.equals(DirtyType.FORCE_REBUILD)) {
+      getDirtyBuildingState().markForceRebuild();
+      return null;
+    }
+    // The caller may be simultaneously trying to mark this node dirty and changed, and the dirty
+    // thread may have lost the race, but it is the caller's responsibility not to try to mark
+    // this node changed twice. The end result of racing markers must be a changed node, since one
+    // of the markers is trying to mark the node changed.
+    Preconditions.checkState(
+        dirtyType.equals(DirtyType.CHANGE) != isChanged(),
+        "Cannot mark node dirty twice or changed twice: %s",
+        this);
     Preconditions.checkState(value == null, "Value should have been reset already %s", this);
-    if (isChanged != isChanged()) {
-      if (isChanged) {
-        getDirtyBuildingState().markChanged();
-      }
-      // If !isChanged, then this call made no changes to the node, but redundancy is a property of
-      // the sequence of markDirty calls, not their effects.
-      return FromDirtyMarkedDirtyResult.NOT_REDUNDANT;
+    if (dirtyType.equals(DirtyType.CHANGE)) {
+      // If the changed marker lost the race, we just need to mark changed in this method -- all
+      // other work was done by the dirty marker.
+      getDirtyBuildingState().markChanged();
     }
-    return FromDirtyMarkedDirtyResult.REDUNDANT;
+    return null;
   }
 
   @Override
@@ -514,7 +522,7 @@
   @Override
   public synchronized void forceRebuild() {
     Preconditions.checkState(getNumTemporaryDirectDeps() == signaledDeps, this);
-    getDirtyBuildingState().forceChanged();
+    getDirtyBuildingState().forceRebuild();
   }
 
   @Override