mirror of https://github.com/jenkinsci/jenkins.git
Merge branch 'master' into renovate/node-24.x
This commit is contained in:
commit
0d3fe0a2d3
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
- "master"
|
||||
jobs:
|
||||
label:
|
||||
if: ${{ github.repository_owner == 'jenkinsci' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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)}"/>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>`,
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue