Add interface that allows Gerrit to check whether a request is cancelled

At the moment Gerrit doesn't get to know when a client disconnects until
the response is written. This means that requests continue to run even
if the is client is no longer waiting for a response. This is bad,
especially for very long-running requests, as these requests keep server
threads occupied. If there is an issue with a certain endpoint or
repository and requests get stuck, this can cause the server running out
of threads so that all users get blocked. To avoid this situation Gerrit
should be able to recognize if the client disconnected and then abort
the request to free up the thread. This may not be possible for all kind
of requests (e.g. HTTP requests) but where possible we want to make it
work. This change is the first step towards this goal.

The interface is designed in such a way that it's possible to return a
reason for the cancellation. This allows to differentiate between
cancellations from the user and cancellations due to exceeded deadlines.

Note, the RequestStateProvider interface is not a regular extension
point where extensions can be registered via Guice. Instead
implementations will be stored in a ThreadLocal variable so that they
are accessible during the request execution. Since requests will need to
check often whether the request is cancelled, it's better to avoid any
overhead that comes with injecting extension point implementations via
Guice.

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Ie1b7d337fbd4bbdd8f426ac111ea28ec88066722
diff --git a/java/com/google/gerrit/server/cancellation/RequestStateProvider.java b/java/com/google/gerrit/server/cancellation/RequestStateProvider.java
new file mode 100644
index 0000000..e1716eb
--- /dev/null
+++ b/java/com/google/gerrit/server/cancellation/RequestStateProvider.java
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 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.google.gerrit.server.cancellation;
+
+import com.google.gerrit.common.Nullable;
+
+/** Interface that provides information about the state of the current request. */
+public interface RequestStateProvider {
+  /**
+   * Checks whether the current request is cancelled.
+   *
+   * <p>Invoked by Gerrit to check whether the current request is cancelled and should be aborted.
+   *
+   * <p>If the current request is cancelled {@link OnCancelled#onCancel(Reason, String)} is invoked
+   * on the provided callback.
+   *
+   * @param onCancelled callback that should be invoked if the request is cancelled
+   */
+  void checkIfCancelled(OnCancelled onCancelled);
+
+  /** Callback interface to be invoked if a request is cancelled. */
+  interface OnCancelled {
+    /**
+     * Callback that is invoked if the request is cancelled.
+     *
+     * @param reason the reason for the cancellation of the request
+     * @param message an optional message providing details about the cancellation
+     */
+    void onCancel(Reason reason, @Nullable String message);
+  }
+
+  /** Reason why a request is cancelled. */
+  enum Reason {
+    /** The client got disconnected or has cancelled the request. */
+    CLIENT_CLOSED_REQUEST,
+
+    /** The deadline that the client provided for the request exceeded. */
+    CLIENT_PROVIDED_DEADLINE_EXCEEDED,
+
+    /**
+     * A server-side deadline for the request exceeded.
+     *
+     * <p>Server-side deadlines are usually configurable, but may also be hard-coded.
+     */
+    SERVER_DEADLINE_EXCEEDED;
+  }
+}