Merge "New option to schedule the Git garbage collection as a background task"

This commit is contained in:
David Pursehouse 2016-01-07 04:28:37 +00:00 committed by Gerrit Code Review
commit dc2da0d4d5
4 changed files with 130 additions and 10 deletions

View File

@ -844,6 +844,34 @@ The response is the streamed output of the garbage collection.
done.
----
==== Asynchronous Execution
The option `async` allows to schedule a background task that asynchronously
executes a Git garbage collection.
The `Location` header of the response refers to the link:rest-api-config.html#get-task[background task]
which allows to inspect the progress of its execution. In case of asynchronous
execution the `show_progress` option is ignored.
.Request
----
POST /projects/plugins%2Freplication/gc HTTP/1.0
Content-Type: application/json;charset=UTF-8
{
"async": true
}
----
The response is empty.
.Response
----
HTTP/1.1 202 Accepted
Content-Disposition: attachment
Location: https:<host>/a/config/server/tasks/383a0602
----
[[ban-commit]]
=== Ban Commit
--
@ -2212,6 +2240,8 @@ collection.
Whether progress information should be shown.
|`aggressive` |`false` if not set|
Whether an aggressive garbage collection should be done.
|`async` |`false` if not set|
Whether the garbage collection should run asynchronously.
|=============================
[[head-input]]

View File

@ -36,6 +36,11 @@ public abstract class Response<T> {
return new Impl<>(201, value);
}
/** HTTP 202 Accepted: accepted as background task. */
public static Accepted accepted(String location) {
return new Accepted(location);
}
/** HTTP 204 No Content: typically used when the resource is deleted. */
@SuppressWarnings("unchecked")
public static <T> Response<T> none() {
@ -168,4 +173,33 @@ public abstract class Response<T> {
return String.format("[302 Redirect] %s", location);
}
}
/** Accepted as task for asynchronous execution. */
public static final class Accepted {
private final String location;
private Accepted(String url) {
this.location = url;
}
public String location() {
return location;
}
@Override
public int hashCode() {
return location.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof Accepted
&& ((Accepted) o).location.equals(location);
}
@Override
public String toString() {
return String.format("[202 Accepted] %s", location);
}
}
}

View File

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static java.math.RoundingMode.CEILING;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import static javax.servlet.http.HttpServletResponse.SC_CREATED;
@ -347,6 +348,11 @@ public class RestApiServlet extends HttpServlet {
CacheHeaders.setNotCacheable(res);
res.sendRedirect(((Response.Redirect) result).location());
return;
} else if (result instanceof Response.Accepted) {
CacheHeaders.setNotCacheable(res);
res.setStatus(SC_ACCEPTED);
res.setHeader(HttpHeaders.LOCATION, ((Response.Accepted)result).location());
return;
} else {
CacheHeaders.setNotCacheable(res);
}

View File

@ -20,13 +20,19 @@ import com.google.gerrit.common.data.GarbageCollectionResult;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.project.GarbageCollect.Input;
import com.google.gerrit.server.util.IdGenerator;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
@ -42,22 +48,61 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
public static class Input {
public boolean showProgress;
public boolean aggressive;
public boolean async;
}
private final boolean canGC;
private GarbageCollection.Factory garbageCollectionFactory;
private final GarbageCollection.Factory garbageCollectionFactory;
private final WorkQueue workQueue;
private final Provider<String> canonicalUrl;
@Inject
GarbageCollect(
GitRepositoryManager repoManager,
GarbageCollection.Factory garbageCollectionFactory) {
GarbageCollect(GitRepositoryManager repoManager,
GarbageCollection.Factory garbageCollectionFactory, WorkQueue workQueue,
@CanonicalWebUrl Provider<String> canonicalUrl) {
this.workQueue = workQueue;
this.canonicalUrl = canonicalUrl;
this.canGC = repoManager instanceof LocalDiskRepositoryManager;
this.garbageCollectionFactory = garbageCollectionFactory;
}
@SuppressWarnings("resource")
@Override
public BinaryResult apply(final ProjectResource rsrc, final Input input) {
public Object apply(ProjectResource rsrc, Input input) {
Project.NameKey project = rsrc.getNameKey();
if (input.async) {
return applyAsync(project, input);
} else {
return applySync(project, input);
}
}
private Response.Accepted applyAsync(final Project.NameKey project, final Input input) {
Runnable job = new Runnable() {
@Override
public void run() {
runGC(project, input, null);
}
@Override
public String toString() {
return "Run " + (input.aggressive ? "aggressive " : "")
+ "garbage collection on project " + project.get();
}
};
@SuppressWarnings("unchecked")
WorkQueue.Task<Void> task =
(WorkQueue.Task<Void>) workQueue.getDefaultQueue().submit(job);
String location = canonicalUrl.get() + "a/config/server/tasks/"
+ IdGenerator.format(task.getTaskId());
return Response.accepted(location);
}
@SuppressWarnings("resource")
private BinaryResult applySync(final Project.NameKey project,
final Input input) {
return new BinaryResult() {
@Override
public void writeTo(OutputStream out) throws IOException {
@ -69,10 +114,8 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
}
};
try {
GarbageCollectionResult result =
garbageCollectionFactory.create().run(
Collections.singletonList(rsrc.getNameKey()), input.aggressive,
input.showProgress ? writer : null);
PrintWriter progressWriter = input.showProgress ? writer : null;
GarbageCollectionResult result = runGC(project, input, progressWriter);
String msg = "Garbage collection completed successfully.";
if (result.hasErrors()) {
for (GarbageCollectionResult.Error e : result.getErrors()) {
@ -104,6 +147,13 @@ public class GarbageCollect implements RestModifyView<ProjectResource, Input>,
.disableGzip();
}
GarbageCollectionResult runGC(Project.NameKey project,
Input input, PrintWriter progressWriter) {
return garbageCollectionFactory.create().run(
Collections.singletonList(project), input.aggressive,
progressWriter);
}
@Override
public UiAction.Description getDescription(ProjectResource rsrc) {
return new UiAction.Description()