QueueIdStrategy (#9020)

This commit is contained in:
Jesse Glick authored and GitHub committed 2024-03-18 15:59:19 +01:00
1 parent 229724953b
commit c0f27461fc
5 files changed
+126 -35

No files matched your search

+24 -22
View File
@@ -102,7 +102,6 @@ import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
@@ -113,6 +112,7 @@ import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import jenkins.model.queue.AsynchronousExecution;
import jenkins.model.queue.CompositeCauseOfBlockage;
import jenkins.model.queue.QueueIdStrategy;
import jenkins.model.queue.QueueItem;
import jenkins.security.QueueItemAuthenticator;
import jenkins.security.QueueItemAuthenticatorProvider;
@@ -124,6 +124,7 @@ import jenkins.util.Timer;
import net.jcip.annotations.GuardedBy;
import org.jenkinsci.remoting.RoleChecker;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
@@ -369,9 +370,20 @@ public class Queue extends ResourceController implements Saveable {
/**
* Simple queue state persistence object.
*/
static class State {
public long counter;
public List<Item> items = new ArrayList<>();
@Restricted(Beta.class)
public static final class State {
public /* almost final */ List<Item> items = new ArrayList<>();
public /* almost final */ Map<String, Object> properties = new HashMap<>();
private Object readResolve() {
if (items == null) {
items = new ArrayList<>();
}
if (properties == null) {
properties = new HashMap<>();
}
return this;
}
}
/**
@@ -391,21 +403,18 @@ public class Queue extends ResourceController implements Saveable {
Object unmarshaledObj = new XmlFile(XSTREAM, queueFile).read();
List items;
State state;
if (unmarshaledObj instanceof State) {
State state = (State) unmarshaledObj;
state = (State) unmarshaledObj;
items = state.items;
WaitingItem.COUNTER.set(state.counter);
} else {
// backward compatibility - it's an old List queue.xml
items = (List) unmarshaledObj;
long maxId = 0;
for (Object o : items) {
if (o instanceof Item) {
maxId = Math.max(maxId, ((Item) o).id);
}
}
WaitingItem.COUNTER.set(maxId);
state = new State();
state.items.addAll(items);
}
QueueIdStrategy.get().load(state);
for (Object o : items) {
if (o instanceof Task) {
@@ -460,8 +469,7 @@ public class Queue extends ResourceController implements Saveable {
try {
// write out the queue state we want to save
State state = new State();
state.counter = WaitingItem.COUNTER.longValue();
QueueIdStrategy.get().persist(state);
// write out the tasks on the queue
for (Item item : getItems()) {
if (item.task instanceof TransientTask) continue;
@@ -2532,8 +2540,6 @@ public class Queue extends ResourceController implements Saveable {
* {@link Item} in the {@link Queue#waitingList} stage.
*/
public static final class WaitingItem extends Item implements Comparable<WaitingItem> {
private static final AtomicLong COUNTER = new AtomicLong(0);
/**
* This item can be run after this time.
*/
@@ -2541,14 +2547,10 @@ public class Queue extends ResourceController implements Saveable {
public Calendar timestamp;
public WaitingItem(Calendar timestamp, Task project, List<Action> actions) {
super(project, actions, COUNTER.incrementAndGet(), new FutureImpl(project));
super(project, actions, QueueIdStrategy.get().generateIdFor(project, actions), new FutureImpl(project));
this.timestamp = timestamp;
}
static int getCurrentCounterValue() {
return COUNTER.intValue();
}
@Override
public int compareTo(WaitingItem that) {
int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
@@ -0,0 +1,86 @@
package jenkins.model.queue;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Action;
import hudson.model.Queue;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Pluggable strategy to generate queue item IDs as well as persist an optional opaque state whenever the queue is persisted.
*/
@Restricted(Beta.class)
public abstract class QueueIdStrategy implements ExtensionPoint {
private static final QueueIdStrategy DEFAULT = new DefaultStrategy();
public static QueueIdStrategy get() {
var strategies = ExtensionList.lookup(QueueIdStrategy.class);
if (strategies.isEmpty()) {
return DEFAULT;
}
return strategies.get(0);
}
/**
* Persist the state of this strategy.
*/
public void persist(@NonNull Queue.State queueState) {}
/**
* Loads the state of this strategy from a persisted queue state.
*/
public void load(@NonNull Queue.State queueState) {}
/**
* Generates a new ID for the given project and actions.
* @param project The task to be queued.
* @param actions The actions linked the task.
* @return a new queue ID.
*/
public abstract long generateIdFor(@NonNull Queue.Task project, @NonNull List<Action> actions);
/**
* Default implementation if no extension is found. Simply uses a counter.
*/
public static final class DefaultStrategy extends QueueIdStrategy {
private static final AtomicLong COUNTER = new AtomicLong(0);
@Override
public long generateIdFor(Queue.Task project, List<Action> actions) {
return COUNTER.incrementAndGet();
}
@Override
public void persist(Queue.State queueState) {
queueState.properties.put(getClass().getName(), COUNTER.get());
}
@Override
public void load(Queue.State queueState) {
var prop = queueState.properties.get(getClass().getName());
if (prop instanceof Long) {
COUNTER.set((Long) prop);
} else {
queueState.items.stream()
.filter(Queue.Item.class::isInstance)
.map(Queue.Item.class::cast)
.max(Comparator.comparing(Queue.Item::getId))
.ifPresentOrElse(
maxItem -> COUNTER.set(maxItem.getId()),
() -> COUNTER.set(0)
);
}
}
@Restricted(DoNotUse.class) // testing only
public static long getCurrentCounterValue() {
return COUNTER.get();
}
}
}