Merge changes I3fc1cee9,I01a882fb,Ice6d35a0,Ibd7d788d,Ie1b7d337
* changes:
SshCommand: Handle RequestCancelledException for SSH commands
ReceiveCommits: Handle RequestCancelledException for git push
RestApiServlet: Handle RequestCancelledException for REST calls
Define an exception that is thrown if a request is cancelled
Add interface that allows Gerrit to check whether a request is cancelled
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 39db61d..7e6ab58 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -46,6 +46,7 @@
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
+import static javax.servlet.http.HttpServletResponse.SC_REQUEST_TIMEOUT;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
@@ -112,6 +113,7 @@
import com.google.gerrit.server.RequestListener;
import com.google.gerrit.server.audit.ExtendedHttpAuditEvent;
import com.google.gerrit.server.cache.PerThreadCache;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.RevisionResource;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -225,6 +227,7 @@
public static final String XD_METHOD = "$m";
public static final int SC_UNPROCESSABLE_ENTITY = 422;
public static final int SC_TOO_MANY_REQUESTS = 429;
+ public static final int SC_CLIENT_CLOSED_REQUEST = 499;
private static final int HEAP_EST_SIZE = 10 * 8 * 1024; // Presize 10 blocks.
private static final String PLAIN_TEXT = "text/plain";
@@ -709,6 +712,25 @@
messageOr(e, "Quota limit reached"),
e.caching(),
e);
+ } catch (RequestCancelledException e) {
+ cause = Optional.of(e);
+ switch (e.getCancellationReason()) {
+ case CLIENT_CLOSED_REQUEST:
+ statusCode = SC_CLIENT_CLOSED_REQUEST;
+ break;
+ case CLIENT_PROVIDED_DEADLINE_EXCEEDED:
+ case SERVER_DEADLINE_EXCEEDED:
+ statusCode = SC_REQUEST_TIMEOUT;
+ break;
+ }
+
+ StringBuilder msg = new StringBuilder(e.formatCancellationReason());
+ if (e.getCancellationMessage().isPresent()) {
+ msg.append("\n\n");
+ msg.append(e.getCancellationMessage().get());
+ }
+
+ responseBytes = replyError(req, res, statusCode, msg.toString(), e);
} catch (Exception e) {
cause = Optional.of(e);
statusCode = SC_INTERNAL_SERVER_ERROR;
diff --git a/java/com/google/gerrit/server/cancellation/RequestCancelledException.java b/java/com/google/gerrit/server/cancellation/RequestCancelledException.java
new file mode 100644
index 0000000..3c668fb
--- /dev/null
+++ b/java/com/google/gerrit/server/cancellation/RequestCancelledException.java
@@ -0,0 +1,68 @@
+// 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;
+import java.util.Optional;
+import org.apache.commons.lang.WordUtils;
+
+/** Exception to signal that the current request is cancelled and should be aborted. */
+public class RequestCancelledException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private final RequestStateProvider.Reason cancellationReason;
+ private final Optional<String> cancellationMessage;
+
+ /**
+ * Create a {@code RequestCancelledException}.
+ *
+ * @param cancellationReason the reason why the request is cancelled
+ * @param cancellationMessage an optional message providing details about the cancellation
+ */
+ public RequestCancelledException(
+ RequestStateProvider.Reason cancellationReason, @Nullable String cancellationMessage) {
+ super(createMessage(cancellationReason, cancellationMessage));
+ this.cancellationReason = cancellationReason;
+ this.cancellationMessage = Optional.ofNullable(cancellationMessage);
+ }
+
+ private static String createMessage(
+ RequestStateProvider.Reason cancellationReason, @Nullable String message) {
+ StringBuilder messageBuilder = new StringBuilder();
+ messageBuilder.append(String.format("Request cancelled: %s", cancellationReason.name()));
+ if (message != null) {
+ messageBuilder.append(String.format(" (%s)", message));
+ }
+ return messageBuilder.toString();
+ }
+
+ /** Returns the reason why the request is cancelled. */
+ public RequestStateProvider.Reason getCancellationReason() {
+ return cancellationReason;
+ }
+
+ /** Returns the cancellation reason as a user-readable string. */
+ public String formatCancellationReason() {
+ return WordUtils.capitalizeFully(cancellationReason.name().replaceAll("_", " "));
+ }
+
+ /**
+ * Returns a message providing details about the cancellation, or {@link Optional#empty()} if none
+ * is available.
+ */
+ public Optional<String> getCancellationMessage() {
+ return cancellationMessage;
+ }
+}
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;
+ }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 454df66..d074f1e 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -114,6 +114,7 @@
import com.google.gerrit.server.RequestListener;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.approval.ApprovalsUtil;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
import com.google.gerrit.server.change.AttentionSetUnchangedOp;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.NotifyResolver;
@@ -640,8 +641,17 @@
Task commandProgress = progress.beginSubTask("refs", UNKNOWN);
commands =
commands.stream().map(c -> wrapReceiveCommand(c, commandProgress)).collect(toList());
- processCommandsUnsafe(commands, progress);
- rejectRemaining(commands, INTERNAL_SERVER_ERROR);
+
+ try {
+ processCommandsUnsafe(commands, progress);
+ rejectRemaining(commands, INTERNAL_SERVER_ERROR);
+ } catch (RequestCancelledException e) {
+ StringBuilder msg = new StringBuilder(e.formatCancellationReason());
+ if (e.getCancellationMessage().isPresent()) {
+ msg.append(String.format(" (%s)", e.getCancellationMessage().get()));
+ }
+ rejectRemaining(commands, msg.toString());
+ }
// This sends error messages before the 'done' string of the progress monitor is sent.
// Currently, the test framework relies on this ordering to understand if pushes completed
diff --git a/java/com/google/gerrit/sshd/SshCommand.java b/java/com/google/gerrit/sshd/SshCommand.java
index c94b25c..93c6c2c 100644
--- a/java/com/google/gerrit/sshd/SshCommand.java
+++ b/java/com/google/gerrit/sshd/SshCommand.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.DynamicOptions;
import com.google.gerrit.server.RequestInfo;
import com.google.gerrit.server.RequestListener;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.logging.PerformanceLogContext;
import com.google.gerrit.server.logging.PerformanceLogger;
@@ -61,6 +62,12 @@
RequestInfo.builder(RequestInfo.RequestType.SSH, user, traceContext).build();
requestListeners.runEach(l -> l.onRequest(requestInfo));
SshCommand.this.run();
+ } catch (RequestCancelledException e) {
+ StringBuilder msg = new StringBuilder(e.formatCancellationReason());
+ if (e.getCancellationMessage().isPresent()) {
+ msg.append(String.format(" (%s)", e.getCancellationMessage().get()));
+ }
+ stderr.println(msg.toString());
} finally {
stdout.flush();
stderr.flush();
diff --git a/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
new file mode 100644
index 0000000..29d54cc
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/CancellationIT.java
@@ -0,0 +1,206 @@
+// 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.acceptance.rest;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.httpd.restapi.RestApiServlet.SC_CLIENT_CLOSED_REQUEST;
+import static org.apache.http.HttpStatus.SC_REQUEST_TIMEOUT;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
+import com.google.gerrit.server.cancellation.RequestStateProvider;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import java.util.List;
+import org.junit.Test;
+
+public class CancellationIT extends AbstractDaemonTest {
+ @Inject private ExtensionRegistry extensionRegistry;
+
+ @Test
+ public void handleClientDisconnected() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ // Simulate a request cancellation by throwing RequestCancelledException. In contrast to
+ // an actual request cancellation this allows us to verify the HTTP status code that is
+ // set when a request is cancelled.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_CLOSED_REQUEST, /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/" + name("new"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_CLIENT_CLOSED_REQUEST);
+ assertThat(response.getEntityContent()).isEqualTo("Client Closed Request");
+ }
+ }
+
+ @Test
+ public void handleClientDeadlineExceeded() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_PROVIDED_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/" + name("new"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
+ assertThat(response.getEntityContent()).isEqualTo("Client Provided Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleServerDeadlineExceeded() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/" + name("new"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
+ assertThat(response.getEntityContent()).isEqualTo("Server Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleRequestCancellationWithMessage() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED, "deadline = 10m");
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ RestResponse response = adminRestSession.put("/projects/" + name("new"));
+ assertThat(response.getStatusCode()).isEqualTo(SC_REQUEST_TIMEOUT);
+ assertThat(response.getEntityContent())
+ .isEqualTo("Server Deadline Exceeded\n\ndeadline = 10m");
+ }
+ }
+
+ @Test
+ public void handleClientDisconnectedForPush() throws Exception {
+ CommitValidationListener commitValidationListener =
+ new CommitValidationListener() {
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ // Simulate a request cancellation by throwing RequestCancelledException. In contrast to
+ // an actual request cancellation this allows us verify the error message that is sent
+ // to the client.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_CLOSED_REQUEST, /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertErrorStatus("Client Closed Request");
+ }
+ }
+
+ @Test
+ public void handleClientDeadlineExceededForPush() throws Exception {
+ CommitValidationListener commitValidationListener =
+ new CommitValidationListener() {
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_PROVIDED_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertErrorStatus("Client Provided Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleServerDeadlineExceededForPush() throws Exception {
+ CommitValidationListener commitValidationListener =
+ new CommitValidationListener() {
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertErrorStatus("Server Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleRequestCancellationWithMessageForPush() throws Exception {
+ CommitValidationListener commitValidationListener =
+ new CommitValidationListener() {
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ // Simulate an exceeded deadline by throwing RequestCancelledException.
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED, "deadline = 10m");
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(commitValidationListener)) {
+ PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+ PushOneCommit.Result r = push.to("refs/heads/master");
+ r.assertErrorStatus("Server Deadline Exceeded (deadline = 10m)");
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java
new file mode 100644
index 0000000..2cb9637
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCancellationIT.java
@@ -0,0 +1,102 @@
+// 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.acceptance.ssh;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.ExtensionRegistry;
+import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.server.cancellation.RequestCancelledException;
+import com.google.gerrit.server.cancellation.RequestStateProvider;
+import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@UseSsh
+public class SshCancellationIT extends AbstractDaemonTest {
+ @Inject private ExtensionRegistry extensionRegistry;
+
+ @Test
+ public void handleClientDisconnected() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_CLOSED_REQUEST, /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project " + name("new"));
+ adminSshSession.assertFailure("Client Closed Request");
+ }
+ }
+
+ @Test
+ public void handleClientDeadlineExceeded() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.CLIENT_PROVIDED_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project " + name("new"));
+ adminSshSession.assertFailure("Client Provided Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleServerDeadlineExceeded() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED,
+ /* cancellationMessage= */ null);
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project " + name("new"));
+ adminSshSession.assertFailure("Server Deadline Exceeded");
+ }
+ }
+
+ @Test
+ public void handleRequestCancellationWithMessage() throws Exception {
+ ProjectCreationValidationListener projectCreationListener =
+ new ProjectCreationValidationListener() {
+ @Override
+ public void validateNewProject(CreateProjectArgs args) throws ValidationException {
+ throw new RequestCancelledException(
+ RequestStateProvider.Reason.SERVER_DEADLINE_EXCEEDED, "deadline = 10m");
+ }
+ };
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(projectCreationListener)) {
+ adminSshSession.exec("gerrit create-project " + name("new"));
+ adminSshSession.assertFailure("Server Deadline Exceeded (deadline = 10m)");
+ }
+ }
+}