Skip to content

Commit 8892602

Browse files
committed
try to lock as few resources as possible
adding a struct class for better code clarity
1 parent 1b5aed9 commit 8892602

File tree

3 files changed

+99
-15
lines changed

3 files changed

+99
-15
lines changed

src/main/java/org/jenkins/plugins/lockableresources/LockableResourcesManager.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import net.sf.json.JSONObject;
3535

3636
import org.apache.commons.lang.StringUtils;
37+
import org.jenkins.plugins.lockableresources.queue.LockableResourcesCandidatesStruct;
3738
import org.jenkins.plugins.lockableresources.queue.LockableResourcesStruct;
3839
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;
3940
import org.jenkins.plugins.lockableresources.queue.QueuedContextStruct;
@@ -638,9 +639,9 @@ public boolean configure(StaplerRequest req, JSONObject json)
638639
public synchronized Set<LockableResource> checkResourcesAvailability(List<LockableResourcesStruct> requiredResourcesList,
639640
@Nullable PrintStream logger, @Nullable List<String> lockedResourcesAboutToBeUnlocked) {
640641

641-
// Build possible resources for each requirement
642-
Map<List<LockableResource>, Integer> allCandidates = new HashMap<>();
642+
List<LockableResourcesCandidatesStruct> requiredResourcesCandidatesList = new ArrayList<>();
643643

644+
// Build possible resources for each requirement
644645
for (LockableResourcesStruct requiredResources : requiredResourcesList) {
645646
// get possible resources
646647
int requiredAmount = 0; // 0 means all
@@ -662,25 +663,21 @@ public synchronized Set<LockableResource> checkResourcesAvailability(List<Lockab
662663
requiredAmount = candidates.size();
663664
}
664665

665-
allCandidates.put(candidates, requiredAmount);
666+
requiredResourcesCandidatesList.add(new LockableResourcesCandidatesStruct(candidates, requiredAmount));
666667
}
667668

668669
// Process freed resources
669-
Map<Map.Entry<List<LockableResource>, Integer>, List<LockableResource>> currentSelection = new HashMap<>();
670670
int totalSelected = 0;
671671

672-
for (Map.Entry<List<LockableResource>, Integer> entry : allCandidates.entrySet()) {
673-
List<LockableResource> candidates = entry.getKey();
674-
int requiredAmount = entry.getValue();
675-
672+
for (LockableResourcesCandidatesStruct requiredResources : requiredResourcesCandidatesList) {
676673
// start with an empty set of selected resources
677674
List<LockableResource> selected = new ArrayList<LockableResource>();
678675

679676
// some resources might be already locked, but will be freed.
680677
// Determine if these resources can be reused
681678
if (lockedResourcesAboutToBeUnlocked != null) {
682-
for (LockableResource candidate : candidates) {
683-
if (selected.size() >= requiredAmount) {
679+
for (LockableResource candidate : requiredResources.candidates) {
680+
if (selected.size() >= requiredResources.requiredAmount) {
684681
break;
685682
}
686683
if (lockedResourcesAboutToBeUnlocked.contains(candidate.getName())) {
@@ -690,7 +687,7 @@ public synchronized Set<LockableResource> checkResourcesAvailability(List<Lockab
690687
}
691688

692689
totalSelected += selected.size();
693-
currentSelection.put(entry, selected);
690+
requiredResources.selected = selected;
694691
}
695692

696693
// if none of the currently locked resources can be reused,
@@ -702,11 +699,24 @@ public synchronized Set<LockableResource> checkResourcesAvailability(List<Lockab
702699
// Find remaining resources
703700
Set<LockableResource> allSelected = new HashSet<>();
704701

705-
for (Map.Entry<Map.Entry<List<LockableResource>, Integer>, List<LockableResource>> entry : currentSelection.entrySet()) {
706-
List<LockableResource> candidates = entry.getKey().getKey();
707-
List<LockableResource> selected = entry.getValue();
708-
int requiredAmount = entry.getKey().getValue();
702+
for (LockableResourcesCandidatesStruct requiredResources : requiredResourcesCandidatesList) {
703+
List<LockableResource> candidates = requiredResources.candidates;
704+
List<LockableResource> selected = requiredResources.selected;
705+
int requiredAmount = requiredResources.requiredAmount;
706+
707+
// Try and re-use as many previously selected resources first
708+
List<LockableResource> alreadySelectedCandidates = new ArrayList<>(candidates);
709+
alreadySelectedCandidates.retainAll(allSelected);
710+
for (LockableResource rs : alreadySelectedCandidates) {
711+
if (selected.size() >= requiredAmount) {
712+
break;
713+
}
714+
if (!rs.isReserved() && !rs.isLocked()) {
715+
selected.add(rs);
716+
}
717+
}
709718

719+
candidates.removeAll(alreadySelectedCandidates);
710720
for (LockableResource rs : candidates) {
711721
if (selected.size() >= requiredAmount) {
712722
break;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.jenkins.plugins.lockableresources.queue;
2+
3+
import java.util.List;
4+
5+
import org.jenkins.plugins.lockableresources.LockableResource;
6+
7+
public class LockableResourcesCandidatesStruct {
8+
9+
public List<LockableResource> candidates;
10+
public int requiredAmount;
11+
public List<LockableResource> selected;
12+
13+
public LockableResourcesCandidatesStruct(List<LockableResource> candidates, int requiredAmount) {
14+
this.candidates = candidates;
15+
this.requiredAmount = requiredAmount;
16+
}
17+
}

src/test/java/org/jenkins/plugins/lockableresources/LockStepTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,4 +1076,61 @@ public void evaluate() throws Throwable {
10761076
}
10771077
});
10781078
}
1079+
1080+
@Test
1081+
public void lockWithLabelAndLabeledResourceQuantity() {
1082+
story.addStep(new Statement() {
1083+
@Override
1084+
public void evaluate() throws Throwable {
1085+
LockableResourcesManager.get().createResourceWithLabel("resource1", "label1");
1086+
LockableResourcesManager.get().createResourceWithLabel("resource2", "label1");
1087+
LockableResourcesManager.get().createResourceWithLabel("resource3", "label1");
1088+
LockableResourcesManager.get().createResourceWithLabel("resource4", "label1");
1089+
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
1090+
p.setDefinition(new CpsFlowDefinition(
1091+
"lock(resource: 'resource4', extra: [[resource: 'resource2'], [label: 'label1', quantity: 2]]) {\n" +
1092+
" semaphore 'wait-inside'\n" +
1093+
"}\n" +
1094+
"echo 'Finish'"
1095+
));
1096+
// #1 should lock as few resources as possible
1097+
WorkflowRun b1 = p.scheduleBuild2(0).waitForStart();
1098+
SemaphoreStep.waitForStart("wait-inside/1", b1);
1099+
1100+
WorkflowJob p2 = story.j.jenkins.createProject(WorkflowJob.class, "p2");
1101+
p2.setDefinition(new CpsFlowDefinition(
1102+
"lock(label: 'label1', quantity: 3) {\n" +
1103+
" semaphore 'wait-inside-quantity3'\n" +
1104+
"}\n" +
1105+
"echo 'Finish'"
1106+
));
1107+
WorkflowRun b2 = p2.scheduleBuild2(0).waitForStart();
1108+
story.j.waitForMessage("[Label: label1, Quantity: 3] is locked, waiting...", b2);
1109+
story.j.waitForMessage("Found 2 available resource(s). Waiting for correct amount: 3.", b2);
1110+
1111+
WorkflowJob p3 = story.j.jenkins.createProject(WorkflowJob.class, "p3");
1112+
p3.setDefinition(new CpsFlowDefinition(
1113+
"lock(label: 'label1', quantity: 2) {\n" +
1114+
" semaphore 'wait-inside-quantity2'\n" +
1115+
"}\n" +
1116+
"echo 'Finish'"
1117+
));
1118+
WorkflowRun b3 = p3.scheduleBuild2(0).waitForStart();
1119+
// While 2 continues waiting, 3 can continue directly
1120+
SemaphoreStep.waitForStart("wait-inside-quantity2/1", b3);
1121+
// Let 3 finish
1122+
SemaphoreStep.success("wait-inside-quantity2/1", null);
1123+
story.j.waitForMessage("Finish", b3);
1124+
1125+
// Unlock resources
1126+
SemaphoreStep.success("wait-inside/1", null);
1127+
story.j.waitForMessage("Lock released on resource [{resource4},{resource2},{Label: label1, Quantity: 2},]", b1);
1128+
1129+
// #2 gets the lock
1130+
story.j.waitForMessage("Lock acquired on [Label: label1, Quantity: 3]", b2);
1131+
SemaphoreStep.success("wait-inside-quantity3/1", null);
1132+
story.j.waitForMessage("Finish", b2);
1133+
}
1134+
});
1135+
}
10791136
}

0 commit comments

Comments
 (0)