When rescheduling due to in-flight push log also the in-flight task ID

When a push operation was rescheduled due to an in-flight push we logged
a line like:

   [79bda07a] Rescheduling replication to https://host/foo.git to avoid collision with an in-flight push.

This log was already useful but it didn't tell us which push task was
actually in-flight.

Add the ID of the in-flight push task to the log:

   [79bda07a] Rescheduling replication to https://host/foo.git to avoid collision with the in-flight push [80ceb18b].

This information is especially useful when a task gets repeatedly
rescheduled and we need to identify the root cause of that.

Change-Id: I7866f964cab1e4f479b0d7d62ae4aac3019fab4b
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
index 5a3b92c..bff96ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/Destination.java
@@ -488,18 +488,19 @@
     }
   }
 
-  boolean requestRunway(PushOne op) {
+  RunwayStatus requestRunway(PushOne op) {
     synchronized (stateLock) {
       if (op.wasCanceled()) {
-        return false;
+        return RunwayStatus.canceled();
       }
       pending.remove(op.getURI());
-      if (inFlight.containsKey(op.getURI())) {
-        return false;
+      PushOne inFlightOp = inFlight.get(op.getURI());
+      if (inFlightOp != null) {
+        return RunwayStatus.denied(inFlightOp.getId());
       }
       inFlight.put(op.getURI(), op);
     }
-    return true;
+    return RunwayStatus.allowed();
   }
 
   void notifyFinished(PushOne op) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
index 09187a0..5c24c74 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/PushOne.java
@@ -251,6 +251,10 @@
     return states.toArray(new ReplicationState[states.size()]);
   }
 
+  public int getId() {
+    return id;
+  }
+
   void addStates(ListMultimap<String, ReplicationState> states) {
     stateMap.putAll(states);
   }
@@ -297,10 +301,13 @@
     // created and scheduled for a future point in time.)
     //
     MDC.put(ID_MDC_KEY, HexFormat.fromInt(id));
-    if (!pool.requestRunway(this)) {
-      if (!canceled) {
+    RunwayStatus status = pool.requestRunway(this);
+    if (!status.isAllowed()) {
+      if (!status.isCanceled()) {
         repLog.info(
-            "Rescheduling replication to {} to avoid collision with an in-flight push.", uri);
+            "Rescheduling replication to {} to avoid collision with the in-flight push [{}].",
+            uri,
+            HexFormat.fromInt(status.getInFlightPushId()));
         pool.reschedule(this, Destination.RetryReason.COLLISION);
       }
       return;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/RunwayStatus.java b/src/main/java/com/googlesource/gerrit/plugins/replication/RunwayStatus.java
new file mode 100644
index 0000000..bcb1e2f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/RunwayStatus.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 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.
+
+package com.googlesource.gerrit.plugins.replication;
+
+public class RunwayStatus {
+  public static RunwayStatus allowed() {
+    return new RunwayStatus(true, 0);
+  }
+
+  public static RunwayStatus canceled() {
+    return new RunwayStatus(false, 0);
+  }
+
+  public static RunwayStatus denied(int inFlightPushId) {
+    return new RunwayStatus(false, inFlightPushId);
+  }
+
+  private final boolean allowed;
+  private final int inFlightPushId;
+
+  private RunwayStatus(boolean allowed, int inFlightPushId) {
+    this.allowed = allowed;
+    this.inFlightPushId = inFlightPushId;
+  }
+
+  public boolean isAllowed() {
+    return allowed;
+  }
+
+  public boolean isCanceled() {
+    return !allowed && inFlightPushId == 0;
+  }
+
+  public int getInFlightPushId() {
+    return inFlightPushId;
+  }
+}