Merge branch 'master' into renovate/node-24.x

This commit is contained in:
Kris Stern 2025-09-18 00:13:48 +08:00 committed by GitHub
commit 0d3fe0a2d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 355 additions and 117 deletions

View File

@ -7,6 +7,7 @@ on:
jobs:
post:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'jenkinsci' }}
steps:
- name: Post on Discourse
uses: roots/discourse-topic-github-release-action@c30dc233349b7c6f24f52fb1c659cc64f13b5474 # v1.0.1

View File

@ -18,7 +18,7 @@ jobs:
pull-requests: write # to add label to PR (release-drafter/release-drafter)
contents: write # to create a github release (release-drafter/release-drafter)
if: github.repository_owner == 'jenkinsci'
if: ${{ github.repository_owner == 'jenkinsci' }}
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"

View File

@ -9,7 +9,7 @@ permissions:
jobs:
main:
if: github.event.pull_request.user.login != 'dependabot[bot]'
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' && github.repository_owner == 'jenkinsci' }}
runs-on: ubuntu-latest
steps:
- name: Label conflicting PRs

View File

@ -9,6 +9,7 @@ permissions:
jobs:
determine-version:
if: ${{ github.repository_owner == 'jenkinsci' }}
runs-on: ubuntu-latest
outputs:
project-version: ${{ steps.set-version.outputs.project-version }}

View File

@ -6,6 +6,7 @@ on:
- "master"
jobs:
label:
if: ${{ github.repository_owner == 'jenkinsci' }}
runs-on: ubuntu-latest
permissions:
issues: write

View File

@ -41,7 +41,7 @@ THE SOFTWARE.
<commons-fileupload2.version>2.0.0-M4</commons-fileupload2.version>
<groovy.version>2.4.21</groovy.version>
<jelly.version>1.1-jenkins-20250731</jelly.version>
<stapler.version>2030.v88a_855365981</stapler.version>
<stapler.version>2033.va_95221851a_23</stapler.version>
</properties>
<dependencyManagement>
@ -63,7 +63,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.2.10</version>
<version>6.2.11</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -71,7 +71,7 @@ THE SOFTWARE.
<!-- https://docs.spring.io/spring-security/reference/6.3/getting-spring-security.html#getting-maven-no-boot -->
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.5.3</version>
<version>6.5.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@ -113,7 +113,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<version>3.6.1</version>
<executions>
<execution>
<goals>

View File

@ -57,7 +57,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-bom</artifactId>
<version>2.10.3</version>
<version>2.10.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@ -85,6 +85,7 @@ import java.nio.channels.ClosedByInterruptException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@ -1348,6 +1349,26 @@ public class Queue extends ResourceController implements Saveable {
return queue._tryWithLock(runnable);
}
}
/**
* Invokes the supplied {@link Runnable} if the {@link Queue} lock was obtained within the given timeout.
*
* @param runnable the operation to perform.
* @return {@code true} if the lock was acquired within the timeout and the operation was performed.
* @since TODO
*/
public static boolean tryWithLock(Runnable runnable, Duration timeout) throws InterruptedException {
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();
return true;
} else {
return queue._tryWithLock(runnable, timeout);
}
}
/**
* Wraps a {@link Runnable} with the {@link Queue} lock held.
*
@ -1435,6 +1456,26 @@ public class Queue extends ResourceController implements Saveable {
}
}
/**
* Invokes the supplied {@link Runnable} if the {@link Queue} lock was obtained within the given timeout
*
* @param runnable the operation to perform.
* @return {@code true} if the lock was acquired within the timeout and the operation was performed.
* @since TODO
*/
protected boolean _tryWithLock(Runnable runnable, Duration timeout) throws InterruptedException {
if (lock.tryLock(timeout.toNanos(), TimeUnit.NANOSECONDS)) {
try {
runnable.run();
} finally {
lock.unlock();
}
return true;
} else {
return false;
}
}
/**
* 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.

View File

@ -114,7 +114,7 @@ public class TimeZoneProperty extends UserProperty {
@Override
public @NonNull UserPropertyCategory getUserPropertyCategory() {
return UserPropertyCategory.get(UserPropertyCategory.Account.class);
return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
}
}

View File

@ -32,7 +32,6 @@ import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.Run;
import hudson.util.RunList;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
@ -165,20 +164,12 @@ public class LogRotator extends BuildDiscarder {
Run lstb = removeLastBuild ? null : job.getLastStableBuild();
if (numToKeep != -1) {
// Note that RunList.size is deprecated, and indeed here we are loading all the builds of the job.
// However we would need to load the first numToKeep anyway, just to skip over them;
// and we would need to load the rest anyway, to delete them.
// (Using RunMap.headMap would not suffice, since we do not know if some recent builds have been deleted for other reasons,
// so simply subtracting numToKeep from the currently last build number might cause us to delete too many.)
RunList<? extends Run<?, ?>> builds = job.getBuilds();
for (Run r : builds.subList(Math.min(builds.size(), numToKeep), builds.size())) {
if (shouldKeepRun(r, lsb, lstb)) {
continue;
}
job.getBuildsAsMap().entrySet().stream().skip(numToKeep).map(Map.Entry::getValue)
.filter(r -> !shouldKeepRun(r, lsb, lstb)).forEach(r -> {
LOGGER.log(FINE, "{0} is to be removed", r);
try { r.delete(); }
catch (IOException ex) { exceptionMap.computeIfAbsent(r, key -> new HashSet<>()).add(ex); }
}
});
}
if (daysToKeep != -1) {
@ -199,15 +190,12 @@ public class LogRotator extends BuildDiscarder {
}
if (artifactNumToKeep != null && artifactNumToKeep != -1) {
RunList<? extends Run<?, ?>> builds = job.getBuilds();
for (Run r : builds.subList(Math.min(builds.size(), artifactNumToKeep), builds.size())) {
if (shouldKeepRun(r, lsb, lstb)) {
continue;
}
job.getBuildsAsMap().entrySet().stream().skip(artifactNumToKeep).map(Map.Entry::getValue)
.filter(r -> !shouldKeepRun(r, lsb, lstb)).forEach(r -> {
LOGGER.log(FINE, "{0} is to be purged of artifacts", r);
try { r.deleteArtifacts(); }
catch (IOException ex) { exceptionMap.computeIfAbsent(r, key -> new HashSet<>()).add(ex); }
}
});
}
if (artifactDaysToKeep != null && artifactDaysToKeep != -1) {

View File

@ -140,6 +140,35 @@ public class Nodes implements PersistenceRoot {
}
}
/**
* Adds a node if a node with the given name doesn't already exist. This is equivalent to
*
* <pre>
* if (nodes.getNode(node.getNodeName()) == null) {
* nodes.addNode(node);
* }
* </pre>
*
* except that it happens atomically.
*
* @param node the new node.
* @return True if the node was added. False otherwise (indicating a node with the given name already exists)
* @throws IOException if the list of nodes could not be persisted.
* @since TODO
*/
public boolean addNodeIfAbsent(final @NonNull Node node) throws IOException {
if (ENFORCE_NAME_RESTRICTIONS) {
Jenkins.checkGoodName(node.getNodeName());
}
Node old = nodes.putIfAbsent(node.getNodeName(), node);
if (old == null) {
handleAddedNode(node, null);
return true;
}
return false;
}
/**
* Adds a node. If a node of the same name already exists then that node will be replaced.
*
@ -153,34 +182,38 @@ public class Nodes implements PersistenceRoot {
Node old = nodes.put(node.getNodeName(), node);
if (node != old) {
node.onLoad(this, node.getNodeName());
jenkins.updateNewComputer(node);
jenkins.trimLabels(node, old);
// TODO there is a theoretical race whereby the node instance is updated/removed after lock release
try {
node.save();
} 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);
}
handleAddedNode(node, old);
}
}
private void handleAddedNode(final @NonNull Node node, final Node old) throws IOException {
node.onLoad(this, node.getNodeName());
jenkins.updateNewComputer(node);
jenkins.trimLabels(node, old);
// TODO there is a theoretical race whereby the node instance is updated/removed after lock release
try {
node.save();
} 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);
}
});
throw e;
}
if (old != null) {
NodeListener.fireOnUpdated(old, node);
} else {
NodeListener.fireOnCreated(node);
}
}
});
throw e;
}
if (old != null) {
NodeListener.fireOnUpdated(old, node);
} else {
NodeListener.fireOnCreated(node);
}
}

View File

@ -82,11 +82,6 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
* from concurrent modifications, where another thread deletes a build while one thread iterates them.
*
* <p>
* Some of the {@link SortedMap} operations are inefficiently implemented, by
* loading all the build records eagerly. We hope to replace
* these implementations by more efficient lazy-loading ones as we go.
*
* <p>
* Object lock of {@code this} is used to make sure mutation occurs sequentially.
* That is, ensure that only one thread is actually calling {@link #retrieve(File)} and
* updating {@link jenkins.model.lazy.AbstractLazyLoadRunMap#core}.
@ -116,7 +111,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
}
@Override
public Set<Entry<Integer, R>> entrySet() {
public Set<Map.Entry<Integer, R>> entrySet() {
assert baseDirInitialized();
return adapter.entrySet();
}

View File

@ -71,7 +71,7 @@ public final class BuildReference<R> {
/**
* check if reference holder set.
* means there war a try to load build object and we have some result of that try
* means there was a try to load build object and we have some result of that try
*
* @return true if there was a try to
*/

View File

@ -97,7 +97,7 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
}
@Override
public Set<Entry<Integer, R>> entrySet() {
public Set<Map.Entry<Integer, R>> entrySet() {
return entrySet;
}
@ -171,7 +171,7 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
public Iterator<Integer> iterator() {
return new AdaptedIterator<>(BuildReferenceMapAdapter.this.entrySet().iterator()) {
@Override
protected Integer adapt(Entry<Integer, R> e) {
protected Integer adapt(Map.Entry<Integer, R> e) {
return e.getKey();
}
};
@ -227,7 +227,7 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
public Iterator<R> iterator() {
return new AdaptedIterator<>(BuildReferenceMapAdapter.this.entrySet().iterator()) {
@Override
protected R adapt(Entry<Integer, R> e) {
protected R adapt(Map.Entry<Integer, R> e) {
return e.getValue();
}
};
@ -239,7 +239,7 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
}
}
private class EntrySetAdapter extends AbstractSet<Entry<Integer, R>> {
private class EntrySetAdapter extends AbstractSet<Map.Entry<Integer, R>> {
@Override
public int size() {
return BuildReferenceMapAdapter.this.core.size();
@ -268,13 +268,24 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
}
@Override
public Iterator<Entry<Integer, R>> iterator() {
public Iterator<Map.Entry<Integer, R>> iterator() {
return new Iterator<>() {
private Entry<Integer, R> current;
private final Iterator<Entry<Integer, R>> it = Iterators.removeNull(Iterators.map(
private Map.Entry<Integer, R> current;
private final Iterator<Map.Entry<Integer, R>> it = Iterators.removeNull(Iterators.map(
BuildReferenceMapAdapter.this.core.entrySet().iterator(), coreEntry -> {
R v = BuildReferenceMapAdapter.this.resolver.resolveBuildRef(coreEntry.getValue());
return v == null ? null : new AbstractMap.SimpleEntry<>(coreEntry.getKey(), v);
BuildReference<R> ref = coreEntry.getValue();
if (!ref.isSet()) {
R r = resolver.resolveBuildRef(ref);
// load not loaded or unloadable build
if (r == null) {
return null;
}
return new EntryAdapter(coreEntry, r);
}
if (ref.isUnloadable()) {
return null;
}
return new EntryAdapter(coreEntry);
}));
@Override
@ -283,7 +294,7 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
}
@Override
public Entry<Integer, R> next() {
public Map.Entry<Integer, R> next() {
return current = it.next();
}
@ -303,6 +314,59 @@ class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements Sor
}
}
private class EntryAdapter implements Entry<Integer, R> {
private final Map.Entry<Integer, BuildReference<R>> coreEntry;
private volatile R resolvedValue;
EntryAdapter(Map.Entry<Integer, BuildReference<R>> coreEntry) {
this(coreEntry, null);
}
EntryAdapter(Map.Entry<Integer, BuildReference<R>> coreEntry, R resolvedValue) {
this.coreEntry = coreEntry;
this.resolvedValue = resolvedValue;
}
private Map.Entry<Integer, R> getResolvedEntry() {
return new AbstractMap.SimpleEntry<>(getKey(), getValue());
}
@Override
public Integer getKey() {
return coreEntry.getKey();
}
@Override
public R getValue() {
R value = resolvedValue;
if (value != null) {
return value;
}
return resolvedValue = resolver.resolveBuildRef(coreEntry.getValue());
}
@Override
public R setValue(R value) {
// BuildReferenceAdapter is read only
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return getResolvedEntry().toString();
}
@Override
public boolean equals(Object o) {
return (o instanceof Map.Entry<?, ?>) && getResolvedEntry().equals(o);
}
@Override
public int hashCode() {
return getResolvedEntry().hashCode();
}
}
/**
* An interface for resolving build references into actual build instances
* and extracting basic metadata from them.

View File

@ -1,5 +1,5 @@
# This file is under the MIT License by authors
caused_by=oorspronkelijk veroorzaakt door:
started_by_project=Gestart door stroomopwaarts project <a class="model-link model-link--float" href="{3}/{2}">{0}</a>, bouwpoging <a class="model-link model-link--float" href="{3}/{2}{1}/">{1}</a>
started_by_project=Gestart door upstreamproject <a class="model-link model-link--float" href="{3}/{2}">{0}</a>, bouwpoging <a class="model-link model-link--float" href="{3}/{2}{1}/">{1}</a>
started_by_project_with_deleted_build=Gestart door stroomopwaarts project <a class="model-link model-link--float" href="{3}/{2}">{0}</a>, bouwpoging {1}

View File

@ -28,10 +28,10 @@ Changes\ in\ dependency=Wijzigingen in afhankelijkheid
beingExecuted=Build wordt uitgevoerd voor {0}
detail=detail
Not\ yet\ determined=Nog niet bepaald
Downstream\ Builds=Stroomafwaartse bouwpogingen
Downstream\ Builds=Downstream bouwpogingen
Failed\ to\ determine=Kon niet bepaald worden
log=log
Upstream\ Builds=Stroomopwaartse bouwpogingen
Upstream\ Builds=Upstream bouwpogingen
none=Geen
Permalinks=Permanente referenties
Build\ number=Nummer bouwpoging

View File

@ -27,15 +27,6 @@ THE SOFTWARE.
<l:ajax>
<div>
${%Controls how Jenkins starts this agent.}
<dl>
<j:forEach var="d" items="${h.getComputerLauncherDescriptors()}">
<dt><b>${d.displayName}</b></dt>
<dd>
<st:include class="${d.clazz}" page="help.jelly" optional="true"/>
</dd>
</j:forEach>
</dl>
</div>
</l:ajax>
</j:jelly>

View File

@ -49,21 +49,8 @@ THE SOFTWARE.
<f:slave-mode name="mode" node="${it}" />
<!-- TODO: should be packaged as a tag -->
<j:set var="itDescriptor" value="${descriptor}"/>
<f:dropdownList name="slave.launcher" title="${%Launch method}"
help="${descriptor.getHelpFile('launcher')}">
<j:forEach var="d" items="${descriptor.computerLauncherDescriptors(it)}">
<f:dropdownListBlock value="${d.clazz.name}" name="${d.displayName}"
selected="${it.launcher.descriptor==d}"
title="${d.displayName}">
<j:set var="descriptor" value="${d}"/>
<j:set var="instance"
value="${it.launcher.descriptor==d ? it.launcher : null}"/>
<f:class-entry descriptor="${d}" />
<st:include from="${d}" page="${d.configPage}" optional="true"/>
</f:dropdownListBlock>
</j:forEach>
</f:dropdownList>
<j:set var="computerLauncherDescriptors" value="${descriptor.computerLauncherDescriptors(it)}"/>
<f:dropdownDescriptorSelector title="${%Launch method}" field="launcher" descriptors="${computerLauncherDescriptors}"/>
<!-- pointless to show this if there's only one option, which is the default -->
<j:set var="retentionStrategies" value="${descriptor.retentionStrategyDescriptors(it)}"/>

View File

@ -34,7 +34,7 @@
"eslint": "9.35.0",
"eslint-config-prettier": "10.1.8",
"eslint-formatter-checkstyle": "8.40.0",
"globals": "16.3.0",
"globals": "16.4.0",
"handlebars-loader": "1.7.3",
"mini-css-extract-plugin": "2.9.4",
"postcss": "8.5.6",

View File

@ -84,7 +84,7 @@ THE SOFTWARE.
<!-- RequireUpperBoundDeps between bootstrap5-api and echarts-api -->
<groupId>io.jenkins.plugins</groupId>
<artifactId>font-awesome-api</artifactId>
<version>7.0.0-851.vd1feb_218a_a_63</version>
<version>7.0.1-859.v128d3a_efb_6e5</version>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
@ -238,7 +238,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1419.v2337d1ceceef</version>
<version>1447.v4cb_b_539b_5321</version>
<scope>test</scope>
</dependency>
<dependency>
@ -262,7 +262,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>856.v4c352b_3a_b_23e</version>
<version>858.vb_b_eb_9a_7ea_99e</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -91,6 +91,7 @@ import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -99,10 +100,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.logging.Level;
@ -451,6 +457,106 @@ public class QueueTest {
});
}
@Test
void tryWithTimeoutSuccessfullyAcquired() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
final CountDownLatch task1Started = new CountDownLatch(1);
final CountDownLatch task1Release = new CountDownLatch(1);
Future<Void> task1 = executor.submit(Queue.wrapWithLock(() -> {
task1Started.countDown();
try {
task1Release.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}));
// Wait for the first task to be started to ensure it is running and has the lock
task1Started.await();
// Create a task that will need to wait until the first task is complete that will fail to acquire.
final AtomicBoolean task2Result = new AtomicBoolean(false);
boolean acquired = Queue.tryWithLock(() -> {
task2Result.set(true);
}, Duration.ofMillis(10));
assertFalse(acquired);
assertFalse(task2Result.get());
// Now release the first task and wait (with a long timeout) and we should succeed at getting the lock.
final AtomicBoolean task3Result = new AtomicBoolean(false);
task1Release.countDown();
acquired = Queue.tryWithLock(() -> {
task3Result.set(true);
}, Duration.ofSeconds(30));
// First task should complete
task1.get();
// Task 2 should have acquired and completed
assertTrue(acquired);
assertTrue(task3Result.get());
} finally {
executor.shutdownNow();
}
}
@Test
void tryWithTimeoutFailedToAcquire() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
// Submit one task that will block indefinitely until released
final CountDownLatch task1Started = new CountDownLatch(1);
final CountDownLatch task1Release = new CountDownLatch(1);
Future<Void> task1 = executor.submit(Queue.wrapWithLock(() -> {
task1Started.countDown();
try {
task1Release.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}));
// Wait for task 1 to start
task1Started.await();
// Try to acquire lock with 50ms timeout, expecting that it cannot be acquired
final AtomicBoolean task2Complete = new AtomicBoolean(false);
boolean result = Queue.tryWithLock(() -> {
task2Complete.set(true);
}, Duration.ofMillis(50));
// Results should indicate the task did not run
assertFalse(result);
assertFalse(task2Complete.get());
// Now release the first task and wait for it to finish
task1Release.countDown();
task1.get();
} finally {
executor.shutdownNow();
}
}
@Test
void tryWithTimeoutImmediatelyAcquired() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
final AtomicBoolean taskComplete = new AtomicBoolean(false);
boolean result = Queue.tryWithLock(() -> {
taskComplete.set(true);
}, Duration.ofMillis(1));
assertTrue(result);
assertTrue(taskComplete.get());
} finally {
executor.shutdownNow();
}
}
@Issue("JENKINS-27256")
@Test
void inQueueTaskLookupByAPI() throws Exception {

View File

@ -34,10 +34,12 @@ import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
@ -226,6 +228,32 @@ class NodesTest {
assertThat(retentionStrategyA.checkCount, equalTo(0));
}
@Test
void addIfAbsentAddsNewNode() throws Exception {
Node newNode = r.createSlave("foo", "", null);
r.jenkins.removeNode(newNode);
assertThat(r.jenkins.getNodesObject().getNodes().size(), equalTo(0));
boolean result = r.jenkins.getNodesObject().addNodeIfAbsent(newNode);
assertTrue(result);
assertThat(r.jenkins.getNodesObject().getNodes().size(), equalTo(1));
assertNotNull(r.jenkins.getNode("foo"));
}
@Test
void addIfAbsentDoesNotReplaceOldNode() throws Exception {
Node oldNode = r.createSlave("foo", "labels1", null);
r.jenkins.removeNode(oldNode);
Node newNode = r.createSlave("foo", "labels2", null);
r.jenkins.removeNode(newNode);
assertThat(r.jenkins.getNodesObject().getNodes().size(), equalTo(0));
r.jenkins.addNode(oldNode);
assertThat(r.jenkins.getNodesObject().getNodes().size(), equalTo(1));
boolean result = r.jenkins.getNodesObject().addNodeIfAbsent(newNode);
assertFalse(result);
r.jenkins.getNodesObject().load();
assertThat(r.jenkins.getNode("foo").getLabelString(), equalTo("labels1"));
}
public static class MockRetentionStrategy extends RetentionStrategy.Always {
private int checkCount = 0;

View File

@ -304,7 +304,7 @@ THE SOFTWARE.
<!-- detached after 1.561 -->
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>matrix-project</artifactId>
<version>856.v4c352b_3a_b_23e</version>
<version>858.vb_b_eb_9a_7ea_99e</version>
<type>hpi</type>
</artifactItem>
<artifactItem>
@ -407,7 +407,7 @@ THE SOFTWARE.
<!-- dependency of bootstrap5-api and echarts-api -->
<groupId>io.jenkins.plugins</groupId>
<artifactId>font-awesome-api</artifactId>
<version>7.0.0-851.vd1feb_218a_a_63</version>
<version>7.0.1-859.v128d3a_efb_6e5</version>
<type>hpi</type>
</artifactItem>
@ -527,7 +527,7 @@ THE SOFTWARE.
<!-- dependency of trilead-api -->
<groupId>io.jenkins.plugins</groupId>
<artifactId>gson-api</artifactId>
<version>2.13.1-153.vb_3d0c48a_a_b_4a_</version>
<version>2.13.2-173.va_a_092315913c</version>
<type>hpi</type>
</artifactItem>
<artifactItem>

View File

@ -821,16 +821,16 @@ function registerMinMaxValidator(e) {
}
if (isInteger(this.value)) {
const valueInt = parseInt(this.value);
// Ensure the value is an integer
if (min !== null && isInteger(min) && max !== null && isInteger(max)) {
// Both min and max attributes are available
if (min <= max) {
const minInt = parseInt(min);
const maxInt = parseInt(max);
if (minInt <= maxInt) {
// Add the validator if min <= max
if (
parseInt(min) > parseInt(this.value) ||
parseInt(this.value) > parseInt(max)
) {
if (minInt > valueInt || valueInt > maxInt) {
// The value is out of range
updateValidationArea(
this.targetElement,
@ -850,7 +850,8 @@ function registerMinMaxValidator(e) {
) {
// There is only 'min' available
if (parseInt(min) > parseInt(this.value)) {
const minInt = parseInt(min);
if (minInt > valueInt) {
updateValidationArea(
this.targetElement,
`<div class="error">This value should be larger than ${min}</div>`,
@ -868,7 +869,8 @@ function registerMinMaxValidator(e) {
) {
// There is only 'max' available
if (parseInt(max) < parseInt(this.value)) {
const maxInt = parseInt(max);
if (maxInt < valueInt) {
updateValidationArea(
this.targetElement,
`<div class="error">This value should be less than ${max}</div>`,

View File

@ -3981,10 +3981,10 @@ __metadata:
languageName: node
linkType: hard
"globals@npm:16.3.0":
version: 16.3.0
resolution: "globals@npm:16.3.0"
checksum: 10c0/c62dc20357d1c0bf2be4545d6c4141265d1a229bf1c3294955efb5b5ef611145391895e3f2729f8603809e81b30b516c33e6c2597573844449978606aad6eb38
"globals@npm:16.4.0":
version: 16.4.0
resolution: "globals@npm:16.4.0"
checksum: 10c0/a14b447a78b664b42f6d324e8675fcae6fe5e57924fecc1f6328dce08af9b2ca3a3138501e1b1f244a49814a732dc60cfc1aa24e714e0b64ac8bd18910bfac90
languageName: node
linkType: hard
@ -4386,7 +4386,7 @@ __metadata:
eslint: "npm:9.35.0"
eslint-config-prettier: "npm:10.1.8"
eslint-formatter-checkstyle: "npm:8.40.0"
globals: "npm:16.3.0"
globals: "npm:16.4.0"
handlebars: "npm:4.7.8"
handlebars-loader: "npm:1.7.3"
hotkeys-js: "npm:3.12.2"