Skip to content
Permalink
Browse files

Merge pull request #95 from stephenc/jenkins-35112-redux

[JENKINS-35112] Re-implement based on JENKINS-35160 impl in core
  • Loading branch information...
stephenc committed Mar 15, 2017
2 parents d43aa4d + 1d06afb commit 37eeaf496b6e30450bc59158a0ae91bb37b9074b
@@ -41,21 +41,27 @@
import hudson.model.AbstractItem;
import hudson.model.Action;
import hudson.model.AllView;
import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.Executor;
import hudson.model.Failure;
import hudson.model.HealthReport;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.ItemGroupMixIn;
import hudson.model.Items;
import hudson.model.Job;
import hudson.model.ModifiableViewGroup;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.model.View;
import hudson.model.ViewGroupMixIn;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.RunListener;
import hudson.model.queue.WorkUnit;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.search.SearchItem;
@@ -82,6 +88,8 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -120,6 +128,7 @@
import org.kohsuke.stapler.interceptor.RequirePOST;

import static hudson.Util.fixEmpty;
import static hudson.model.queue.Executables.getParentOf;

/**
* A general-purpose {@link ItemGroup}.
@@ -128,7 +137,7 @@
* <b>Extending Folders UI</b><br>
* As any other {@link Item} type, folder types support extension of UI via {@link Action}s.
* These actions can be persisted or added via {@link TransientActionFactory}.
* See <a href="https://wiki.jenkins-ci.org/display/JENKINS/Action+and+its+family+of+subtypes">this page</a>
* See <a href="https://wiki.jenkins-ci.org/display/JENKINS/Action+and+its+family+of+subtypes">this page</a>
* for more details about actions.
* In folders actions provide the following features:
* <ul>
@@ -232,7 +241,7 @@ protected void init() {
}
if (icon == null) {
icon = newDefaultFolderIcon();
}
}
icon.setOwner(this);

if (folderViews == null) {
@@ -1199,28 +1208,146 @@ public void onDeleted(I item) throws IOException {
public void delete() throws IOException, InterruptedException {
// Some parts copied from AbstractItem.
checkPermission(DELETE);
// delete individual items first
// (disregard whether they would be deletable in isolation)
// JENKINS-34939: do not hold the monitor on this folder while deleting them
// (thus we cannot do this inside performDelete)
SecurityContext orig = ACL.impersonate(ACL.SYSTEM);
// TODO remove once baseline core has JENKINS-35160
boolean responsibleForAbortingBuilds = !ItemDeletion.contains(this);
boolean ownsRegistration = ItemDeletion.register(this);
if (!ownsRegistration && ItemDeletion.isRegistered(this)) {
// we are not the owning thread and somebody else is concurrently deleting this exact item
throw new Failure(Messages.AbstractFolder_BeingDeleted(getPronoun()));
}
try {
for (Item i : new ArrayList<Item>(items.values())) {
try {
i.delete();
} catch (AbortException e) {
throw (AbortException) new AbortException(
"Failed to delete " + i.getFullDisplayName() + " : " + e.getMessage()).initCause(e);
} catch (IOException e) {
throw new IOException("Failed to delete " + i.getFullDisplayName(), e);
// if a build is in progress. Cancel it.
if (responsibleForAbortingBuilds || ownsRegistration) {
Queue queue = Queue.getInstance();
if (this instanceof Queue.Task) {
// clear any items in the queue so they do not get picked up
queue.cancel((Queue.Task) this);
}
// now cancel any child items - this happens after ItemDeletion registration, so we can use a snapshot
for (Queue.Item i : queue.getItems()) {
Item item = ItemDeletion.getItemOf(i.task);
while (item != null) {
if (item == this) {
queue.cancel(i);
break;
}
if (item.getParent() instanceof Item) {
item = (Item) item.getParent();
} else {
break;
}
}
}
// interrupt any builds in progress (and this should be a recursive test so that folders do not pay
// the 15 second delay for every child item). This happens after queue cancellation, so will be
// a complete set of builds in flight
Map<Executor, Queue.Executable> buildsInProgress = new LinkedHashMap<>();
for (Computer c : Jenkins.getActiveInstance().getComputers()) {
for (Executor e : c.getExecutors()) {
WorkUnit workUnit = e.getCurrentWorkUnit();
if (workUnit != null) {
Item item = ItemDeletion.getItemOf(getParentOf(workUnit.getExecutable()));
if (item != null) {
while (item != null) {
if (item == this) {
buildsInProgress.put(e, e.getCurrentExecutable());
e.interrupt(Result.ABORTED);
break;
}
if (item.getParent() instanceof Item) {
item = (Item) item.getParent();
} else {
break;
}
}
}
}
}
for (Executor e : c.getOneOffExecutors()) {
WorkUnit workUnit = e.getCurrentWorkUnit();
if (workUnit != null) {
Item item = ItemDeletion.getItemOf(getParentOf(workUnit.getExecutable()));
if (item != null) {
while (item != null) {
if (item == this) {
buildsInProgress.put(e, e.getCurrentExecutable());
e.interrupt(Result.ABORTED);
break;
}
if (item.getParent() instanceof Item) {
item = (Item) item.getParent();
} else {
break;
}
}
}
}
}
}
if (!buildsInProgress.isEmpty()) {
// give them 15 seconds or so to respond to the interrupt
long expiration = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
// comparison with executor.getCurrentExecutable() == computation currently should always be true
// as we no longer recycle Executors, but safer to future-proof in case we ever revisit recycling
while (!buildsInProgress.isEmpty() && expiration - System.nanoTime() > 0L) {
// we know that ItemDeletion will prevent any new builds in the queue
// ItemDeletion happens-before Queue.cancel so we know that the Queue will stay clear
// Queue.cancel happens-before collecting the buildsInProgress list
// thus buildsInProgress contains the complete set we need to interrupt and wait for
for (Iterator<Map.Entry<Executor, Queue.Executable>> iterator =
buildsInProgress.entrySet().iterator();
iterator.hasNext(); ) {
Map.Entry<Executor, Queue.Executable> entry = iterator.next();
// comparison with executor.getCurrentExecutable() == executable currently should always be
// true as we no longer recycle Executors, but safer to future-proof in case we ever
// revisit recycling.
if (!entry.getKey().isAlive()
|| entry.getValue() != entry.getKey().getCurrentExecutable()) {
iterator.remove();
}
// I don't know why, but we have to keep interrupting
entry.getKey().interrupt(Result.ABORTED);
}
Thread.sleep(50L);
}
if (!buildsInProgress.isEmpty()) {
throw new Failure(Messages.AbstractFolder_FailureToStopBuilds(
buildsInProgress.size(), getFullDisplayName()
));
}
}
}
// END remove once baseline core has JENKINS-35160

// delete individual items first
// (disregard whether they would be deletable in isolation)
// JENKINS-34939: do not hold the monitor on this folder while deleting them
// (thus we cannot do this inside performDelete)
SecurityContext orig = ACL.impersonate(ACL.SYSTEM);
try {
for (Item i : new ArrayList<Item>(items.values())) {
try {
i.delete();
} catch (AbortException e) {
throw (AbortException) new AbortException(
"Failed to delete " + i.getFullDisplayName() + " : " + e.getMessage()).initCause(e);
} catch (IOException e) {
throw new IOException("Failed to delete " + i.getFullDisplayName(), e);
}
}
} finally {
SecurityContextHolder.setContext(orig);
}
synchronized (this) {
performDelete();
}
// TODO remove once baseline core has JENKINS-35160
} finally {
SecurityContextHolder.setContext(orig);
}
synchronized (this) {
performDelete();
if (ownsRegistration) {
ItemDeletion.deregister(this);
}
}
// END remove once baseline core has JENKINS-35160
getParent().onDeleted(AbstractFolder.this);
Jenkins.getActiveInstance().rebuildDependencyGraphAsync();
}
Oops, something went wrong.

0 comments on commit 37eeaf4

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.