Introduce ThrowingRunnable and ThrowingCallable (#11037)

* Introduce `ThrowingRunnable` and `ThrowingCallable`

* Minimize diff

* Minimize diff

* Deprecate `NotReallyRoleSensitiveCallable` https://github.com/jenkinsci/jenkins/pull/11037#discussion_r2323626187

* CheckStyle

---------

Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
This commit is contained in:
Jesse GlickandMark Waite authored and GitHub committed 2025-10-24 09:29:03 -06:00
1 parent 45766b3404
commit ba22ac42b4
15 files changed
+323 -195

No files matched your search

@@ -69,7 +69,6 @@ import jenkins.model.Jenkins;
import jenkins.model.Loadable;
import jenkins.model.queue.ItemDeletion;
import jenkins.security.ExtendedReadRedaction;
import jenkins.security.NotReallyRoleSensitiveCallable;
import jenkins.security.stapler.StaplerNotDispatchable;
import jenkins.util.SystemProperties;
import jenkins.util.xml.XMLUtils;
@@ -914,6 +913,7 @@ public abstract class AbstractItem extends Actionable implements Loadable, Item,
* sources may not be handled.
* @since 1.473
*/
@SuppressWarnings("unchecked")
public void updateByXml(Source source) throws IOException {
checkPermission(CONFIGURE);
XmlFile configXmlFile = getConfigFile();
@@ -934,12 +934,7 @@ public abstract class AbstractItem extends Actionable implements Loadable, Item,
throw new IOException("Expecting " + this.getClass() + " but got " + o.getClass() + " instead");
}
Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<Void, IOException>() {
@Override public Void call() throws IOException {
onLoad(getParent(), getRootDir().getName());
return null;
}
});
Items.runWhileUpdatingByXml(() -> onLoad(getParent(), getRootDir().getName()));
Jenkins.get().rebuildDependencyGraphAsync();
// if everything went well, commit this new version
@@ -967,18 +962,13 @@ public abstract class AbstractItem extends Actionable implements Loadable, Item,
}
@Override
@SuppressWarnings("unchecked")
public void load() throws IOException {
checkPermission(CONFIGURE);
// try to reflect the changes by reloading
getConfigFile().unmarshal(this);
Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<Void, IOException>() {
@Override
public Void call() throws IOException {
onLoad(getParent(), getParent().getItemName(getRootDir(), AbstractItem.this));
return null;
}
});
Items.runWhileUpdatingByXml(() -> onLoad(getParent(), getParent().getItemName(getRootDir(), this)));
Jenkins.get().rebuildDependencyGraphAsync();
}
+28 -32
View File
@@ -54,7 +54,6 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -365,40 +364,37 @@ public class Executor extends Thread implements ModelObject, IExecutor {
SubTask task;
// transition from idle to building.
// perform this state change as an atomic operation wrt other queue operations
task = Queue.withLock(new Callable<>() {
@Override
public SubTask call() throws Exception {
if (!(owner instanceof Jenkins.MasterComputer)) {
if (!owner.isOnline()) {
resetWorkUnit("went off-line before the task's worker thread was ready to execute");
return null;
}
if (owner.getNode() == null) {
resetWorkUnit("was removed before the task's worker thread was ready to execute");
return null;
}
task = Queue.callWithLock(() -> {
if (!(owner instanceof Jenkins.MasterComputer)) {
if (!owner.isOnline()) {
resetWorkUnit("went off-line before the task's worker thread was ready to execute");
return null;
}
// after this point we cannot unwind the assignment of the work unit, if the owner
// is removed or goes off-line then the build will just have to fail.
workUnit.setExecutor(Executor.this);
queue.onStartExecuting(Executor.this);
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName() + " grabbed " + workUnit + " from queue");
SubTask task = workUnit.work;
Executable executable = task.createExecutable();
if (executable == null) {
String displayName = task instanceof Queue.Task ? ((Queue.Task) task).getFullDisplayName() : task.getDisplayName();
LOGGER.log(WARNING, "{0} cannot be run (for example because it is disabled)", displayName);
if (owner.getNode() == null) {
resetWorkUnit("was removed before the task's worker thread was ready to execute");
return null;
}
lock.writeLock().lock();
try {
Executor.this.executable = executable;
} finally {
lock.writeLock().unlock();
}
workUnit.setExecutable(executable);
return task;
}
// after this point we cannot unwind the assignment of the work unit, if the owner
// is removed or goes off-line then the build will just have to fail.
workUnit.setExecutor(Executor.this);
queue.onStartExecuting(Executor.this);
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName() + " grabbed " + workUnit + " from queue");
SubTask _task = workUnit.work;
Executable _executable = _task.createExecutable();
if (_executable == null) {
String displayName = _task instanceof Queue.Task qt ? qt.getFullDisplayName() : _task.getDisplayName();
LOGGER.log(WARNING, "{0} cannot be run (for example because it is disabled)", displayName);
}
lock.writeLock().lock();
try {
executable = _executable;
} finally {
lock.writeLock().unlock();
}
workUnit.setExecutable(_executable);
return _task;
});
Executable executable;
lock.readLock().lock();
@@ -48,7 +48,6 @@ import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import jenkins.model.Jenkins;
import jenkins.security.ExtendedReadRedaction;
import jenkins.security.NotReallyRoleSensitiveCallable;
import jenkins.util.xml.XMLUtils;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerRequest2;
@@ -264,11 +263,7 @@ public abstract class ItemGroupMixIn {
// reload from the new config
final File rootDir = result.getRootDir();
result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<T, IOException>() {
@Override public T call() throws IOException {
return (T) Items.load(parent, rootDir);
}
});
result = Items.callWhileUpdatingByXml(() -> (T) Items.load(parent, rootDir));
result.onCopiedFrom(src);
add(result);
@@ -294,11 +289,7 @@ public abstract class ItemGroupMixIn {
XMLUtils.safeTransform(new StreamSource(xml), new StreamResult(configXml));
// load it
TopLevelItem result = Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<TopLevelItem, IOException>() {
@Override public TopLevelItem call() throws IOException {
return (TopLevelItem) Items.load(parent, dir);
}
});
TopLevelItem result = Items.callWhileUpdatingByXml(() -> (TopLevelItem) Items.load(parent, dir));
boolean hasCreatePermission = acl.getACL().hasCreatePermission2(Jenkins.getAuthentication2(), parent, result.getDescriptor());
boolean applicableIn = result.getDescriptor().isApplicableIn(parent);
+34 -1
View File
@@ -53,6 +53,8 @@ import java.util.function.Predicate;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import jenkins.util.MemoryReductionUtil;
import jenkins.util.ThrowingCallable;
import jenkins.util.ThrowingRunnable;
import org.apache.commons.io.FileUtils;
import org.springframework.security.core.Authentication;
@@ -116,6 +118,23 @@ public class Items {
}
};
/**
* Runs a block while making {@link #currentlyUpdatingByXml} be temporarily true.
* Use this when you are creating or changing an item.
* @param <T> an error type (may be {@link Error})
* @param runnable a block, typically running {@link #load} or {@link Item#onLoad}
* @throws T anything {@code runnable} throws
* @since TODO
*/
public static <T extends Throwable> void runWhileUpdatingByXml(ThrowingRunnable<T> runnable) throws T {
updatingByXml.set(true);
try {
runnable.run();
} finally {
updatingByXml.set(false);
}
}
/**
* Runs a block while making {@link #currentlyUpdatingByXml} be temporarily true.
* Use this when you are creating or changing an item.
@@ -124,6 +143,19 @@ public class Items {
* @param callable a block, typically running {@link #load} or {@link Item#onLoad}
* @return whatever {@code callable} returned
* @throws T anything {@code callable} throws
* @since TODO
*/
public static <V, T extends Throwable> V callWhileUpdatingByXml(ThrowingCallable<V, T> callable) throws T {
updatingByXml.set(true);
try {
return callable.call();
} finally {
updatingByXml.set(false);
}
}
/**
* Prefer {@link #runWhileUpdatingByXml} or {@link #callWhileUpdatingByXml}.
* @since 1.546
*/
public static <V, T extends Throwable> V whileUpdatingByXml(Callable<V, T> callable) throws T {
@@ -138,7 +170,8 @@ public class Items {
/**
* Checks whether we are in the middle of creating or configuring an item via XML.
* Used to determine the {@code newInstance} parameter for {@link Trigger#start}.
* @return true if {@link #whileUpdatingByXml} is currently being called, false for example when merely starting Jenkins or reloading from disk
* @return true if {@link #runWhileUpdatingByXml} or {@link #callWhileUpdatingByXml} is currently being called,
* false for example when merely starting Jenkins or reloading from disk
* @since 1.546
*/
public static boolean currentlyUpdatingByXml() {
+74 -7
View File
@@ -123,6 +123,8 @@ import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.AtmostOneTaskExecutor;
import jenkins.util.Listeners;
import jenkins.util.SystemProperties;
import jenkins.util.ThrowingCallable;
import jenkins.util.ThrowingRunnable;
import jenkins.util.Timer;
import net.jcip.annotations.GuardedBy;
import org.jenkinsci.remoting.RoleChecker;
@@ -1275,6 +1277,22 @@ public class Queue extends ResourceController implements Saveable {
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
* @param runnable the operation to perform.
* @throws T if the runnable throws an exception.
* @since TODO
*/
public static <T extends Throwable> void runWithLock(ThrowingRunnable<T> runnable) throws T {
final Jenkins jenkins = Jenkins.getInstanceOrNull();
// TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
runnable.run();
} else {
queue._runWithLock(runnable);
}
}
/**
* Prefer {@link #runWithLock}.
* @since 1.592
*/
public static void withLock(Runnable runnable) {
@@ -1297,6 +1315,21 @@ public class Queue extends ResourceController implements Saveable {
* @param <T> the type of exception.
* @return the result of the callable.
* @throws T the exception of the callable
* @since TODO
*/
public static <V, T extends Throwable> V callWithLock(ThrowingCallable<V, T> callable) throws T {
final Jenkins jenkins = Jenkins.getInstanceOrNull();
// TODO confirm safe to assume non-null and use getInstance()
final Queue queue = jenkins == null ? null : jenkins.getQueue();
if (queue == null) {
return callable.call();
} else {
return queue._callWithLock(callable);
}
}
/**
* Prefer {@link #callWithLock}.
* @since 1.592
*/
public static <V, T extends Throwable> V withLock(hudson.remoting.Callable<V, T> callable) throws T {
@@ -1311,13 +1344,7 @@ public class Queue extends ResourceController implements Saveable {
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @return the result of the callable.
* @throws Exception if the callable throws an exception.
* Prefer {@link #callWithLock}.
* @since 1.592
*/
public static <V> V withLock(java.util.concurrent.Callable<V> callable) throws Exception {
@@ -1420,6 +1447,25 @@ public class Queue extends ResourceController implements Saveable {
condition.signalAll();
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param runnable the operation to perform.
* @param <T> the type of exception.
* @throws T the exception of the runnable
* @since TODO
*/
@Override
protected <T extends Throwable> void _runWithLock(ThrowingRunnable<T> runnable) throws T {
lock.lock();
try {
runnable.run();
} finally {
lock.unlock();
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
@@ -1476,6 +1522,27 @@ public class Queue extends ResourceController implements Saveable {
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
*
* @param callable the operation to perform.
* @param <V> the type of return value
* @param <T> the type of exception.
* @return the result of the callable.
* @throws T the exception of the callable
* @since TODO
*/
@Override
protected <V, T extends Throwable> V _callWithLock(ThrowingCallable<V, T> callable) throws T {
lock.lock();
try {
return callable.call();
} finally {
lock.unlock();
}
}
/**
* Some operations require to be performed with the {@link Queue} lock held. Use one of these methods rather
* than locking directly on Queue in order to allow for future refactoring.
@@ -31,9 +31,9 @@ import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import jenkins.security.NotReallyRoleSensitiveCallable;
import jenkins.util.ThrowingCallable;
import jenkins.util.ThrowingRunnable;
/**
* Controls mutual exclusion of {@link ResourceList}.
@@ -82,32 +82,25 @@ public class ResourceController {
*/
public void execute(@NonNull Runnable task, final ResourceActivity activity) throws InterruptedException {
final ResourceList resources = activity.getResourceList();
_withLock(new NotReallyRoleSensitiveCallable<Void, InterruptedException>() {
@Override
public Void call() throws InterruptedException {
while (inUse.isCollidingWith(resources)) {
// TODO revalidate the resource list after re-acquiring lock, for now we just let the build fail
_await();
}
// we have a go
inProgress.add(activity);
inUse = ResourceList.union(inUse, resources);
return null;
_runWithLock(() -> {
while (inUse.isCollidingWith(resources)) {
// TODO revalidate the resource list after re-acquiring lock, for now we just let the build fail
_await();
}
// we have a go
inProgress.add(activity);
inUse = ResourceList.union(inUse, resources);
});
try {
task.run();
} finally {
// TODO if AsynchronousExecution, do that later
_withLock(new Runnable() {
@Override
public void run() {
inProgress.remove(activity);
inUse = ResourceList.union(resourceView);
_signalAll();
}
_runWithLock(() -> {
inProgress.remove(activity);
inUse = ResourceList.union(resourceView);
_signalAll();
});
}
}
@@ -122,16 +115,7 @@ public class ResourceController {
* gets to call {@link #execute(Runnable, ResourceActivity)}.
*/
public boolean canRun(final ResourceList resources) {
try {
return _withLock(new Callable<>() {
@Override
public Boolean call() {
return !inUse.isCollidingWith(resources);
}
});
} catch (Exception e) {
throw new IllegalStateException("Inner callable does not throw exception", e);
}
return _callWithLock(() -> !inUse.isCollidingWith(resources));
}
/**
@@ -143,16 +127,7 @@ public class ResourceController {
* This method is used for reporting what's causing the blockage.
*/
public Resource getMissingResource(final ResourceList resources) {
try {
return _withLock(new Callable<>() {
@Override
public Resource call() {
return resources.getConflict(inUse);
}
});
} catch (Exception e) {
throw new IllegalStateException("Inner callable does not throw exception", e);
}
return _callWithLock(() -> resources.getConflict(inUse));
}
/**
@@ -177,12 +152,24 @@ public class ResourceController {
notifyAll();
}
protected <T extends Throwable> void _runWithLock(ThrowingRunnable<T> runnable) throws T {
synchronized (this) {
runnable.run();
}
}
protected void _withLock(Runnable runnable) {
synchronized (this) {
runnable.run();
}
}
protected <V, T extends Throwable> V _callWithLock(ThrowingCallable<V, T> callable) throws T {
synchronized (this) {
return callable.call();
}
}
protected <V> V _withLock(java.util.concurrent.Callable<V> callable) throws Exception {
synchronized (this) {
return callable.call();
+1 -6
View File
@@ -41,7 +41,6 @@ import java.util.function.BiFunction;
import java.util.stream.Collectors;
import jenkins.model.Jenkins;
import jenkins.security.NonSerializableSecurityContext;
import jenkins.security.NotReallyRoleSensitiveCallable;
import org.acegisecurity.acls.sid.PrincipalSid;
import org.acegisecurity.acls.sid.Sid;
import org.kohsuke.accmod.Restricted;
@@ -438,10 +437,6 @@ public abstract class ACL {
}
/**
* Safer variant of {@link #impersonate2(Authentication)} that does not require a finally-block.
* @param auth authentication, such as {@link #SYSTEM2}
* @param body an action to run with this alternate authentication in effect (try {@link NotReallyRoleSensitiveCallable})
* @since 2.266
* @deprecated use try with resources and {@link #as2(Authentication)}
*/
@Deprecated
@@ -455,7 +450,7 @@ public abstract class ACL {
}
/**
* @deprecated use {@link #impersonate2(Authentication, Callable)}
* @deprecated use try with resources and {@link #as2(Authentication)}
* @since 1.587
*/
@Deprecated
@@ -67,17 +67,14 @@ public class ComputerRetentionWork extends AperiodicWork {
protected void doAperiodicRun() {
final long startRun = System.currentTimeMillis();
for (final Computer c : Jenkins.get().getComputers()) {
Queue.withLock(new Runnable() {
@Override
public void run() {
Node n = c.getNode();
if (n != null && n.isHoldOffLaunchUntilSave())
return;
if (!nextCheck.containsKey(c) || startRun > nextCheck.get(c)) {
// at the moment I don't trust strategies to wait more than 60 minutes
final long waitInMins = Math.max(0, Math.min(60, c.getRetentionStrategy().check(c)));
nextCheck.put(c, startRun + TimeUnit.MINUTES.toMillis(waitInMins));
}
Queue.runWithLock(() -> {
Node n = c.getNode();
if (n != null && n.isHoldOffLaunchUntilSave())
return;
if (!nextCheck.containsKey(c) || startRun > nextCheck.get(c)) {
// at the moment I don't trust strategies to wait more than 60 minutes
final long waitInMins = Math.max(0, Math.min(60, c.getRetentionStrategy().check(c)));
nextCheck.put(c, startRun + TimeUnit.MINUTES.toMillis(waitInMins));
}
});
}
@@ -101,7 +101,7 @@ public abstract class RetentionStrategy<T extends Computer> implements Describab
* @since 1.275
*/
public void start(final @NonNull T c) {
Queue.withLock((Runnable) () -> check(c));
Queue.runWithLock(() -> check(c));
}
/**
@@ -211,27 +211,21 @@ public class SimpleScheduledRetentionStrategy extends RetentionStrategy<SlaveCom
new Object[]{c.getName()});
return 0;
} else if (c.isIdle() && c.isAcceptingTasks()) {
Queue.withLock(new Runnable() {
@Override
public void run() {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
Queue.runWithLock(() -> {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
});
} else if (c.isIdle() && !c.isAcceptingTasks()) {
Queue.withLock(new Runnable() {
@Override
public void run() {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
Queue.runWithLock(() -> {
if (c.isIdle()) {
LOGGER.log(INFO, "Disconnecting computer {0} as it has finished all jobs running when "
+ "it completed its scheduled uptime", new Object[]{c.getName()});
c.disconnect(OfflineCause
.create(Messages._SimpleScheduledRetentionStrategy_FinishedUpTime()));
}
});
}
@@ -963,6 +963,7 @@ public class SlaveComputer extends Computer {
@Override
@SuppressFBWarnings(value = "UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR", justification = "TODO needs triage")
@SuppressWarnings("unchecked")
protected void setNode(final Node node) {
super.setNode(node);
launcher = grabLauncher(node);
@@ -971,13 +972,8 @@ public class SlaveComputer extends Computer {
// "constructed==null" test is an ugly hack to avoid launching before the object is fully
// constructed.
if (constructed != null) {
if (node instanceof Slave) {
Queue.withLock(new Runnable() {
@Override
public void run() {
((Slave) node).getRetentionStrategy().check(SlaveComputer.this);
}
});
if (node instanceof Slave slave) {
Queue.runWithLock(() -> slave.getRetentionStrategy().check(SlaveComputer.this));
} else {
connect(false);
}
+29 -41
View File
@@ -196,16 +196,13 @@ public class Nodes implements PersistenceRoot {
} catch (IOException | RuntimeException e) {
// JENKINS-50599: If persisting the node throws an exception, we need to remove the node from
// memory before propagating the exception.
Queue.withLock(new Runnable() {
@Override
public void run() {
nodes.compute(node.getNodeName(), (ignoredNodeName, ignoredNode) -> old);
jenkins.updateComputers(node);
if (old != null) {
jenkins.trimLabels(node, old);
} else {
jenkins.trimLabels(node);
}
Queue.runWithLock(() -> {
nodes.compute(node.getNodeName(), (ignoredNodeName, ignoredNode) -> old);
jenkins.updateComputers(node);
if (old != null) {
jenkins.trimLabels(node, old);
} else {
jenkins.trimLabels(node);
}
});
throw e;
@@ -283,13 +280,10 @@ public class Nodes implements PersistenceRoot {
if (oldOne == nodes.get(oldOne.getNodeName())) {
// use the queue lock until Nodes has a way of directly modifying a single node.
Queue.withLock(new Runnable() {
@Override
public void run() {
Nodes.this.nodes.remove(oldOne.getNodeName());
Nodes.this.nodes.put(newOne.getNodeName(), newOne);
newOne.onLoad(Nodes.this, newOne.getNodeName());
}
Queue.runWithLock(() -> {
Nodes.this.nodes.remove(oldOne.getNodeName());
Nodes.this.nodes.put(newOne.getNodeName(), newOne);
newOne.onLoad(Nodes.this, newOne.getNodeName());
});
updateNode(newOne, false);
if (!newOne.getNodeName().equals(oldOne.getNodeName())) {
@@ -318,16 +312,13 @@ public class Nodes implements PersistenceRoot {
public void removeNode(final @NonNull Node node) throws IOException {
if (node == nodes.get(node.getNodeName())) {
AtomicBoolean match = new AtomicBoolean();
Queue.withLock(new Runnable() {
@Override
public void run() {
Computer c = node.toComputer();
if (c != null) {
c.recordTermination();
c.disconnect(OfflineCause.create(hudson.model.Messages._Hudson_NodeBeingRemoved()));
}
match.set(node == nodes.remove(node.getNodeName()));
Queue.runWithLock(() -> {
Computer c = node.toComputer();
if (c != null) {
c.recordTermination();
c.disconnect(OfflineCause.create(hudson.model.Messages._Hudson_NodeBeingRemoved()));
}
match.set(node == nodes.remove(node.getNodeName()));
});
// no need for a full save() so we just do the minimum
LOGGER.fine(() -> "deleting " + new File(getRootDir(), node.getNodeName()));
@@ -387,21 +378,18 @@ public class Nodes implements PersistenceRoot {
}
}
}
Queue.withLock(new Runnable() {
@Override
public void run() {
newNodes.entrySet().removeIf(stringNodeEntry -> ExtensionList.lookup(NodeListener.class).stream().anyMatch(nodeListener -> {
if (!nodeListener.allowLoad(stringNodeEntry.getValue())) {
LOGGER.log(Level.FINE, () -> "Loading of node " + stringNodeEntry.getKey() + " vetoed by " + nodeListener);
return true;
}
return false;
}));
nodes.entrySet().removeIf(stringNodeEntry -> !(stringNodeEntry.getValue() instanceof EphemeralNode));
nodes.putAll(newNodes);
jenkins.updateComputerList();
jenkins.trimLabels();
}
Queue.runWithLock(() -> {
newNodes.entrySet().removeIf(stringNodeEntry -> ExtensionList.lookup(NodeListener.class).stream().anyMatch(nodeListener -> {
if (!nodeListener.allowLoad(stringNodeEntry.getValue())) {
LOGGER.log(Level.FINE, () -> "Loading of node " + stringNodeEntry.getKey() + " vetoed by " + nodeListener);
return true;
}
return false;
}));
nodes.entrySet().removeIf(stringNodeEntry -> !(stringNodeEntry.getValue() instanceof EphemeralNode));
nodes.putAll(newNodes);
jenkins.updateComputerList();
jenkins.trimLabels();
});
}
@@ -1,15 +1,13 @@
package jenkins.security;
import hudson.remoting.Callable;
import jenkins.util.ThrowingCallable;
import org.jenkinsci.remoting.RoleChecker;
/**
* {@link Callable} adapter for situations where Callable is not used for remoting but
* just as a convenient function that has parameterized return value and exception type.
*
* @author Kohsuke Kawaguchi
* @since 1.587 / 1.580.1
* @deprecated use {@link ThrowingCallable} instead
*/
@Deprecated
public abstract class NotReallyRoleSensitiveCallable<V, T extends Throwable> implements Callable<V, T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
@@ -0,0 +1,54 @@
/*
* The MIT License
*
* Copyright 2025 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.util;
import java.io.Serializable;
import jenkins.security.NotReallyRoleSensitiveCallable;
import org.jenkinsci.remoting.RoleSensitive;
/**
* A task that returns a result and may throw an exception.
* Similar to {@link java.util.concurrent.Callable} except that the exception type can be constrained.
* Similar to {@link hudson.remoting.Callable} or {@link NotReallyRoleSensitiveCallable} except
* <ul>
* <li>It is not {@link Serializable}, which would cause SpotBugs to complain about captured local variables.
* <li>It does not have the {@link RoleSensitive#checkRoles} so it can be a {@link FunctionalInterface}.
* </ul>
* Similar to {@link ThrowingRunnable} but returns a value.
* @param <V> the return type
* @param <T> the checked exception type, or might be {@link RuntimeException}
* @since TODO
*/
@FunctionalInterface
public interface ThrowingCallable<V, T extends Throwable> {
/**
* Computes a result, or throws an exception if unable to do so.
* @return computed result
* @throws T if unable to compute a result
*/
V call() throws T;
}
@@ -0,0 +1,42 @@
/*
* The MIT License
*
* Copyright 2025 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package jenkins.util;
/**
* Represents an operation that does not return a result.
* Similar to {@link Runnable} but can throw a checked exception.
* Similar to {@link ThrowingCallable} but does not return a value.
* @param <T> the checked exception type, or might be {@link RuntimeException}
*/
@FunctionalInterface
public interface ThrowingRunnable<T extends Throwable> {
/**
* Runs this operation.
* @throws T if unable to run
*/
void run() throws T;
}