Skip to content

Commit 3f65280

Browse files
authored
Merge pull request #495 from Dohbedoh/JENKINS-74975
[JENKINS-74975] Fix Semaphore over-allocation of permits / limit all CleanupTasks
2 parents f528356 + 5ab306b commit 3f65280

File tree

1 file changed

+44
-56
lines changed

1 file changed

+44
-56
lines changed

src/main/java/jenkins/branch/WorkspaceLocatorImpl.java

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
import hudson.security.ACLContext;
4444
import hudson.slaves.ComputerListener;
4545
import hudson.slaves.WorkspaceList;
46+
import hudson.util.ClassLoaderSanityThreadFactory;
47+
import hudson.util.DaemonThreadFactory;
48+
import hudson.util.ExceptionCatchingThreadFactory;
49+
import hudson.util.NamingThreadFactory;
4650
import hudson.util.TextFile;
4751
import java.io.BufferedReader;
4852
import java.io.File;
@@ -53,23 +57,31 @@
5357
import java.nio.charset.StandardCharsets;
5458
import java.security.MessageDigest;
5559
import java.security.NoSuchAlgorithmException;
56-
import java.util.concurrent.Semaphore;
60+
import java.util.concurrent.ExecutorService;
5761
import java.util.Iterator;
5862
import java.util.LinkedList;
5963
import java.util.List;
6064
import java.util.Map;
6165
import java.util.Queue;
6266
import java.util.TreeMap;
6367
import java.util.WeakHashMap;
68+
import java.util.concurrent.LinkedBlockingQueue;
69+
import java.util.concurrent.SynchronousQueue;
70+
import java.util.concurrent.ThreadPoolExecutor;
6471
import java.util.concurrent.TimeUnit;
6572
import java.util.logging.Level;
6673
import java.util.logging.Logger;
6774
import java.util.regex.Matcher;
6875
import java.util.regex.Pattern;
76+
import java.util.stream.Collectors;
77+
import java.util.stream.Stream;
6978
import edu.umd.cs.findbugs.annotations.CheckForNull;
7079
import jenkins.MasterToSlaveFileCallable;
7180
import jenkins.model.Jenkins;
81+
import jenkins.security.ImpersonatingExecutorService;
7282
import jenkins.slaves.WorkspaceLocator;
83+
import jenkins.util.ContextResettingExecutorService;
84+
import jenkins.util.ErrorLoggingExecutorService;
7385
import jenkins.util.SystemProperties;
7486
import org.apache.commons.codec.binary.Base32;
7587
import org.apache.commons.lang.StringUtils;
@@ -380,24 +392,49 @@ static String minimize(String name) {
380392
@Extension
381393
public static class Deleter extends ItemListener {
382394

383-
private static /* almost final */ int CLEANUP_THREAD_LIMIT = SystemProperties.getInteger(Deleter.class.getName() + ".CLEANUP_THREAD_LIMIT", Integer.valueOf(0)).intValue();
395+
private static final int CLEANUP_THREAD_LIMIT = SystemProperties.getInteger(Deleter.class.getName() + ".CLEANUP_THREAD_LIMIT", 0);
384396

385-
/** Semaphore for limiting number of scheduled {@link CleanupTask} */
386-
private static Semaphore cleanupPool = new Semaphore(CLEANUP_THREAD_LIMIT, true);
397+
private static final ExecutorService executorService = executorService();
387398

388399
/** Number of {@link CleanupTask} which have been scheduled but not yet completed. */
389400
private static int runningTasks;
390401

402+
private static ExecutorService executorService() {
403+
if (CLEANUP_THREAD_LIMIT > 0) {
404+
ThreadPoolExecutor tpe = new ThreadPoolExecutor(CLEANUP_THREAD_LIMIT, CLEANUP_THREAD_LIMIT, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
405+
new ExceptionCatchingThreadFactory(
406+
new NamingThreadFactory(
407+
new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()),
408+
"Deleter.cleanupTask")));
409+
// Use allowCoreThreadTimeOut to keep a lightweight ThreadPoolExecutor. The Thread pool grows to the
410+
// limit and then queue tasks. Thread will then be removed when idle
411+
tpe.allowCoreThreadTimeOut(true);
412+
return new ContextResettingExecutorService(
413+
new ImpersonatingExecutorService(
414+
new ErrorLoggingExecutorService(tpe),
415+
ACL.SYSTEM2));
416+
} else {
417+
return Computer.threadPoolForRemoting;
418+
}
419+
}
420+
391421
@Override
392422
public void onDeleted(Item item) {
393423
if (!(item instanceof TopLevelItem)) {
394424
return;
395425
}
396426
TopLevelItem tli = (TopLevelItem) item;
397427
Jenkins jenkins = Jenkins.get();
398-
Computer.threadPoolForRemoting.submit(new CleanupTask(tli, jenkins));
399-
// Starts provisioner Thread which is tasked with starting cleanup Threads
400-
new CleanupTaskProvisioner(tli, jenkins.getNodes()).run();
428+
Queue<Node> nodes = Stream
429+
.concat(Stream.of(jenkins), jenkins.getNodes().stream())
430+
.collect(Collectors.toCollection(LinkedList::new));
431+
try {
432+
while (!nodes.isEmpty()){
433+
executorService.execute(new CleanupTask(tli, nodes.remove()));
434+
}
435+
} catch (Exception e) {
436+
LOGGER.log(Level.WARNING, e.getMessage());
437+
}
401438
}
402439

403440
@Override
@@ -412,20 +449,6 @@ public void onLocationChanged(Item item, String oldFullName, String newFullName)
412449
}
413450
}
414451

415-
public void acquireThread() throws InterruptedException {
416-
if (CLEANUP_THREAD_LIMIT <= 0) {
417-
return;
418-
}
419-
cleanupPool.acquire();
420-
}
421-
422-
public void releaseThread() {
423-
if (CLEANUP_THREAD_LIMIT <= 0) {
424-
return;
425-
}
426-
cleanupPool.release();
427-
}
428-
429452
// Visible for testing
430453
static synchronized void waitForTasksToFinish() throws InterruptedException {
431454
while (runningTasks > 0) {
@@ -442,36 +465,6 @@ private static synchronized void taskFinished() {
442465
Deleter.class.notifyAll();
443466
}
444467

445-
private static class CleanupTaskProvisioner implements Runnable{
446-
447-
@NonNull
448-
private final TopLevelItem tli;
449-
450-
@NonNull
451-
private final Queue<Node> nodes;
452-
453-
@NonNull
454-
private final Deleter deleter;
455-
456-
public CleanupTaskProvisioner(TopLevelItem tli, List<Node> nodes) {
457-
this.tli = tli;
458-
this.nodes = new LinkedList<>(nodes);
459-
this.deleter = ExtensionList.lookupSingleton(Deleter.class);
460-
}
461-
462-
@Override
463-
public void run() {
464-
try {
465-
while (!nodes.isEmpty()){
466-
deleter.acquireThread();
467-
Computer.threadPoolForRemoting.submit(new CleanupTask(tli, nodes.remove()));
468-
}
469-
} catch (Exception e) {
470-
LOGGER.log(Level.WARNING, e.getMessage());
471-
}
472-
}
473-
}
474-
475468
private static class CleanupTask implements Runnable {
476469

477470
@NonNull
@@ -480,13 +473,9 @@ private static class CleanupTask implements Runnable {
480473
@NonNull
481474
private final Node node;
482475

483-
@NonNull
484-
private final Deleter deleter;
485-
486476
CleanupTask(TopLevelItem tli, Node node) {
487477
this.tli = tli;
488478
this.node = node;
489-
this.deleter = ExtensionList.lookupSingleton(Deleter.class);
490479
taskStarted();
491480
}
492481

@@ -529,7 +518,6 @@ public void run() {
529518
}
530519
} finally {
531520
t.setName(oldName);
532-
deleter.releaseThread();
533521
taskFinished();
534522
}
535523
}

0 commit comments

Comments
 (0)