/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Red Hat, Inc., Stephen Connolly, Tom Huybrechts
*
* 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 hudson.model;
import hudson.FilePath;
import hudson.Util;
import hudson.model.Queue.Executable;
import hudson.model.queue.Executables;
import hudson.model.queue.SubTask;
import hudson.model.queue.Tasks;
import hudson.model.queue.WorkUnit;
import hudson.security.ACL;
import hudson.util.InterceptingProxy;
import hudson.util.TimeUnit2;
import jenkins.model.CauseOfInterruption;
import jenkins.model.CauseOfInterruption.UserInterruption;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import static hudson.model.queue.Executables.*;
import java.util.Collection;
import static java.util.logging.Level.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.queue.AsynchronousExecution;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Thread that executes builds.
* Since 1.536, {@link Executor}s start threads on-demand.
* <p>Callers should use {@link #isActive()} instead of {@link #isAlive()}.
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public class Executor extends Thread implements ModelObject {
protected final @Nonnull Computer owner;
private final Queue queue;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
@GuardedBy("lock")
private long startTime;
/**
* Used to track when a job was last executed.
*/
private final long creationTime = System.currentTimeMillis();
/**
* Executor number that identifies it among other executors for the same {@link Computer}.
*/
private int number;
/**
* {@link hudson.model.Queue.Executable} being executed right now, or null if the executor is idle.
*/
@GuardedBy("lock")
private Queue.Executable executable;
/**
* Used to mark that the execution is continuing asynchronously even though {@link Executor} as {@link Thread}
* has finished.
*/
@GuardedBy("lock")
private AsynchronousExecution asynchronousExecution;
/**
* When {@link Queue} allocates a work for this executor, this field is set
* and the executor is {@linkplain Thread#start() started}.
*/
@GuardedBy("lock")
private WorkUnit workUnit;
@GuardedBy("lock")
private boolean started;
/**
* When the executor is interrupted, we allow the code that interrupted the thread to override the
* result code it prefers.
*/
@GuardedBy("lock")
private Result interruptStatus;
/**
* Cause of interruption. Access needs to be synchronized.
*/
@GuardedBy("lock")
private final List<CauseOfInterruption> causes = new Vector<CauseOfInterruption>();
public Executor(@Nonnull Computer owner, int n) {
super("Executor #"+n+" for "+owner.getDisplayName());
this.owner = owner;
this.queue = Jenkins.getInstance().getQueue();
this.number = n;
}
@Override
public void interrupt() {
if (Thread.currentThread() == this) {
// If you catch an InterruptedException the "correct" options are limited to one of two choices:
// 1. Propagate the exception;
// 2. Restore the Thread.currentThread().interrupted() flag
// The JVM locking support assumes such behaviour.
// Evil Jenkins overrides the interrupt() method so that when a different thread interrupts this thread
// we abort the build.
// but that causes JENKINS-28690 style deadlocks when the correctly written code does
//
// try {
// ... some long running thing ...
// } catch (InterruptedException e) {
// ... some tidy up
// // restore interrupted flag
// Thread.currentThread().interrupted();
// }
//
// What about why we do not set the Result.ABORTED on this branch?
// That is a good question to ask, the answer is that the only time a thread should be restoring
// its own interrupted flag is when that thread has already been interrupted by another thread
// as such we should assume that the result has already been applied. If that assumption were
// incorrect, then the Run.execute's catch (InterruptedException) block will either set the result
// or have been escaped - in which case the result of the run has been sealed anyway so it does not
// matter.
super.interrupt();
} else {
interrupt(Result.ABORTED);
}
}
void interruptForShutdown() {
interrupt(Result.ABORTED, true);
}
/**
* Interrupt the execution,
* but instead of marking the build as aborted, mark it as specified result.
*
* @since 1.417
*/
public void interrupt(Result result) {
interrupt(result, false);
}
private void interrupt(Result result, boolean forShutdown) {
Authentication a = Jenkins.getAuthentication();
if (a == ACL.SYSTEM)
interrupt(result, forShutdown, new CauseOfInterruption[0]);
else {
// worth recording who did it
// avoid using User.get() to avoid deadlock.
interrupt(result, forShutdown, new UserInterruption(a.getName()));
}
}
/**
* Interrupt the execution. Mark the cause and the status accordingly.
*/
public void interrupt(Result result, CauseOfInterruption... causes) {
interrupt(result, false, causes);
}
private void interrupt(Result result, boolean forShutdown, CauseOfInterruption... causes) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, String.format("%s is interrupted(%s): %s", getDisplayName(), result, Util.join(Arrays.asList(causes),",")), new InterruptedException());
lock.writeLock().lock();
try {
if (!started) {
// not yet started, so simply dispose this
owner.removeExecutor(this);
return;
}
interruptStatus = result;
for (CauseOfInterruption c : causes) {
if (!this.causes.contains(c))
this.causes.add(c);
}
if (asynchronousExecution != null) {
asynchronousExecution.interrupt(forShutdown);
} else {
super.interrupt();
}
} finally {
lock.writeLock().unlock();
}
}
public Result abortResult() {
// this method is almost always called as a result of the current thread being interrupted
// as a result we need to clean the interrupt flag so that the lock's lock method doesn't
// get confused and think it was interrupted while awaiting the lock
Thread.interrupted();
// we need to use a write lock as we may be repeatedly interrupted while processing and
// we need the same lock as used in void interrupt(Result,boolean,CauseOfInterruption...)
// JENKINS-28690
lock.writeLock().lock();
try {
Result r = interruptStatus;
if (r == null) r =
Result.ABORTED; // this is when we programmatically throw InterruptedException instead of calling the interrupt method.
return r;
} finally {
lock.writeLock().unlock();
}
}
/**
* report cause of interruption and record it to the build, if available.
*
* @since 1.425
*/
public void recordCauseOfInterruption(Run<?,?> build, TaskListener listener) {
List<CauseOfInterruption> r;
// atomically get&clear causes.
lock.writeLock().lock();
try {
if (causes.isEmpty()) return;
r = new ArrayList<CauseOfInterruption>(causes);
causes.clear();
} finally {
lock.writeLock().unlock();
}
build.addAction(new InterruptedBuildAction(r));
for (CauseOfInterruption c : r)
c.print(listener);
}
/**
* There are some cases where an executor is started but the node is removed or goes off-line before we are ready
* to start executing the assigned work unit. This method is called to clear the assigned work unit so that
* the {@link Queue#maintain()} method can return the item to the buildable state.
*
* Note: once we create the {@link Executable} we cannot unwind the state and the build will have to end up being
* marked as a failure.
*/
private void resetWorkUnit(String reason) {
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
try {
pw.printf("%s grabbed %s from queue but %s %s. ", getName(), workUnit, owner.getDisplayName(), reason);
if (owner.getTerminatedBy().isEmpty()) {
pw.print("No termination trace available.");
} else {
pw.println("Termination trace follows:");
for (Computer.TerminationRequest request: owner.getTerminatedBy()) {
request.printStackTrace(pw);
}
}
} finally {
pw.close();
}
LOGGER.log(WARNING, writer.toString());
lock.writeLock().lock();
try {
if (executable != null) {
throw new IllegalStateException("Cannot reset the work unit after the executable has been created");
}
workUnit = null;
} finally {
lock.writeLock().unlock();
}
}
@Override
public void run() {
if (!owner.isOnline()) {
resetWorkUnit("went off-line before the task's worker thread started");
owner.removeExecutor(this);
queue.scheduleMaintenance();
return;
}
if (owner.getNode() == null) {
resetWorkUnit("was removed before the task's worker thread started");
owner.removeExecutor(this);
queue.scheduleMaintenance();
return;
}
final WorkUnit workUnit;
lock.writeLock().lock();
try {
startTime = System.currentTimeMillis();
workUnit = this.workUnit;
} finally {
lock.writeLock().unlock();
}
ACL.impersonate(ACL.SYSTEM);
try {
SubTask task;
// transition from idle to building.
// perform this state change as an atomic operation wrt other queue operations
task = Queue.withLock(new java.util.concurrent.Callable<SubTask>() {
@Override
public SubTask call() throws Exception {
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;
}
// 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();
lock.writeLock().lock();
try {
Executor.this.executable = executable;
} finally {
lock.writeLock().unlock();
}
workUnit.setExecutable(executable);
return task;
}
});
Executable executable;
lock.readLock().lock();
try {
if (this.workUnit == null) {
// we called resetWorkUnit, so bail. Outer finally will remove this and schedule queue maintenance
return;
}
executable = this.executable;
} finally {
lock.readLock().unlock();
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
Throwable problems = null;
try {
workUnit.context.synchronizeStart();
// this code handles the behavior of null Executables returned
// by tasks. In such case Jenkins starts the workUnit in order
// to report results to console outputs.
if (executable == null) {
throw new Error("The null Executable has been created for "+workUnit+". The task cannot be executed");
}
if (executable instanceof Actionable) {
for (Action action: workUnit.context.actions) {
((Actionable) executable).addAction(action);
}
}
ACL.impersonate(workUnit.context.item.authenticate());
setName(getName() + " : executing " + executable.toString());
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
} catch (AsynchronousExecution x) {
lock.writeLock().lock();
try {
x.setExecutor(this);
this.asynchronousExecution = x;
} finally {
lock.writeLock().unlock();
}
} catch (Throwable e) {
problems = e;
} finally {
boolean needFinish1;
lock.readLock().lock();
try {
needFinish1 = asynchronousExecution == null;
} finally {
lock.readLock().unlock();
}
if (needFinish1) {
finish1(problems);
}
}
} catch (InterruptedException e) {
LOGGER.log(FINE, getName()+" interrupted",e);
// die peacefully
} catch(Exception | Error e) {
LOGGER.log(SEVERE, "Unexpected executor death", e);
} finally {
if (asynchronousExecution == null) {
finish2();
}
}
}
private void finish1(@CheckForNull Throwable problems) {
if (problems != null) {
// for some reason the executor died. this is really
// a bug in the code, but we don't want the executor to die,
// so just leave some info and go on to build other things
LOGGER.log(Level.SEVERE, "Executor threw an exception", problems);
workUnit.context.abort(problems);
}
long time = System.currentTimeMillis() - startTime;
LOGGER.log(FINE, "{0} completed {1} in {2}ms", new Object[]{getName(), executable, time});
try {
workUnit.context.synchronizeEnd(this, executable, problems, time);
} catch (InterruptedException e) {
workUnit.context.abort(e);
} finally {
workUnit.setExecutor(null);
}
}
private void finish2() {
for (RuntimeException e1 : owner.getTerminatedBy()) {
LOGGER.log(Level.FINE, String.format("%s termination trace", getName()), e1);
}
owner.removeExecutor(this);
if (this instanceof OneOffExecutor) {
owner.remove((OneOffExecutor) this);
}
queue.scheduleMaintenance();
}
@Restricted(NoExternalUse.class)
public void completedAsynchronous(@CheckForNull Throwable error) {
try {
finish1(error);
} finally {
finish2();
}
asynchronousExecution = null;
}
/**
* Returns the current build this executor is running.
*
* @return
* null if the executor is idle.
*/
@Exported
public @CheckForNull Queue.Executable getCurrentExecutable() {
lock.readLock().lock();
try {
return executable;
} finally {
lock.readLock().unlock();
}
}
/**
* Returns causes of interruption.
*
* @return Unmodifiable collection of causes of interruption.
* @since 1.617
*/
public @Nonnull Collection<CauseOfInterruption> getCausesOfInterruption() {
return Collections.unmodifiableCollection(causes);
}
/**
* Returns the current {@link WorkUnit} (of {@link #getCurrentExecutable() the current executable})
* that this executor is running.
*
* @return
* null if the executor is idle.
*/
@Exported
public WorkUnit getCurrentWorkUnit() {
lock.readLock().lock();
try {
return workUnit;
} finally {
lock.readLock().unlock();
}
}
/**
* If {@linkplain #getCurrentExecutable() current executable} is {@link AbstractBuild},
* return the workspace that this executor is using, or null if the build hasn't gotten
* to that point yet.
*/
public FilePath getCurrentWorkspace() {
lock.readLock().lock();
try {
if (executable == null) {
return null;
}
if (executable instanceof AbstractBuild) {
AbstractBuild ab = (AbstractBuild) executable;
return ab.getWorkspace();
}
return null;
} finally {
lock.readLock().unlock();
}
}
/**
* Same as {@link #getName()}.
*/
public String getDisplayName() {
return "Executor #"+getNumber();
}
/**
* Gets the executor number that uniquely identifies it among
* other {@link Executor}s for the same computer.
*
* @return
* a sequential number starting from 0.
*/
@Exported
public int getNumber() {
return number;
}
/**
* Returns true if this {@link Executor} is ready for action.
*/
@Exported
public boolean isIdle() {
lock.readLock().lock();
try {
return workUnit == null && executable == null;
} finally {
lock.readLock().unlock();
}
}
/**
* The opposite of {@link #isIdle()} — the executor is doing some work.
*/
public boolean isBusy() {
lock.readLock().lock();
try {
return workUnit != null || executable != null;
} finally {
lock.readLock().unlock();
}
}
/**
* Check if executor is ready to accept tasks.
* This method becomes the critical one since 1.536, which introduces the
* on-demand creation of executor threads. Callers should use
* this method instead of {@link #isAlive()}, which would be incorrect for
* non-started threads or running {@link AsynchronousExecution}.
* @return true if the executor is available for tasks (usually true)
* @since 1.536
*/
public boolean isActive() {
lock.readLock().lock();
try {
return !started || asynchronousExecution != null || isAlive();
} finally {
lock.readLock().unlock();
}
}
/**
* If currently running in asynchronous mode, returns that handle.
* @since 1.607
*/
public @CheckForNull AsynchronousExecution getAsynchronousExecution() {
lock.readLock().lock();
try {
return asynchronousExecution;
} finally {
lock.readLock().unlock();
}
}
/**
* If this executor is running an {@link AsynchronousExecution} and that execution wants to hide the display
* cell for the executor (because there is another executor displaying the job progress and we don't want to
* confuse the user) then this method will return {@code false} to indicate to {@code executors.jelly} that
* the executor cell should be hidden.
*
* @return {@code true} iff the {@code executorCell.jelly} for this {@link Executor} should be displayed in
* {@code executors.jelly}.
* @since 1.607
* @see AsynchronousExecution#displayCell()
*/
public boolean isDisplayCell() {
AsynchronousExecution asynchronousExecution = getAsynchronousExecution();
return asynchronousExecution == null || asynchronousExecution.displayCell();
}
/**
* Returns true if this executor is waiting for a task to execute.
*/
public boolean isParking() {
lock.readLock().lock();
try {
return !started;
} finally {
lock.readLock().unlock();
}
}
/**
* @deprecated no longer used
*/
@Deprecated
public @CheckForNull Throwable getCauseOfDeath() {
return null;
}
/**
* Returns the progress of the current build in the number between 0-100.
*
* @return -1
* if it's impossible to estimate the progress.
*/
@Exported
public int getProgress() {
long d;
lock.readLock().lock();
try {
if (executable == null) {
return -1;
}
d = Executables.getEstimatedDurationFor(executable);
} finally {
lock.readLock().unlock();
}
if (d <= 0) {
return -1;
}
int num = (int) (getElapsedTime() * 100 / d);
if (num >= 100) {
num = 99;
}
return num;
}
/**
* Returns true if the current build is likely stuck.
*
* <p>
* This is a heuristics based approach, but if the build is suspiciously taking for a long time,
* this method returns true.
*/
@Exported
public boolean isLikelyStuck() {
long d;
long elapsed;
lock.readLock().lock();
try {
if (executable == null) {
return false;
}
elapsed = getElapsedTime();
d = Executables.getEstimatedDurationFor(executable);
} finally {
lock.readLock().unlock();
}
if (d >= 0) {
// if it's taking 10 times longer than ETA, consider it stuck
return d * 10 < elapsed;
} else {
// if no ETA is available, a build taking longer than a day is considered stuck
return TimeUnit2.MILLISECONDS.toHours(elapsed) > 24;
}
}
public long getElapsedTime() {
lock.readLock().lock();
try {
return System.currentTimeMillis() - startTime;
} finally {
lock.readLock().unlock();
}
}
/**
* Returns the number of milli-seconds the currently executing job spent in the queue
* waiting for an available executor. This excludes the quiet period time of the job.
* @since 1.440
*/
public long getTimeSpentInQueue() {
lock.readLock().lock();
try {
return startTime - workUnit.context.item.buildableStartMilliseconds;
} finally {
lock.readLock().unlock();
}
}
/**
* Gets the string that says how long since this build has started.
*
* @return
* string like "3 minutes" "1 day" etc.
*/
public String getTimestampString() {
return Util.getPastTimeString(getElapsedTime());
}
/**
* Computes a human-readable text that shows the expected remaining time
* until the build completes.
*/
public String getEstimatedRemainingTime() {
long d;
lock.readLock().lock();
try {
if (executable == null) {
return Messages.Executor_NotAvailable();
}
d = Executables.getEstimatedDurationFor(executable);
} finally {
lock.readLock().unlock();
}
if (d < 0) {
return Messages.Executor_NotAvailable();
}
long eta = d - getElapsedTime();
if (eta <= 0) {
return Messages.Executor_NotAvailable();
}
return Util.getTimeSpanString(eta);
}
/**
* The same as {@link #getEstimatedRemainingTime()} but return
* it as a number of milli-seconds.
*/
public long getEstimatedRemainingTimeMillis() {
long d;
lock.readLock().lock();
try {
if (executable == null) {
return -1;
}
d = Executables.getEstimatedDurationFor(executable);
} finally {
lock.readLock().unlock();
}
if (d < 0) {
return -1;
}
long eta = d - getElapsedTime();
if (eta <= 0) {
return -1;
}
return eta;
}
/**
* Can't start executor like you normally start a thread.
*
* @see #start(WorkUnit)
*/
@Override
public void start() {
throw new UnsupportedOperationException();
}
/*protected*/ void start(WorkUnit task) {
lock.writeLock().lock();
try {
this.workUnit = task;
super.start();
started = true;
} finally {
lock.writeLock().unlock();
}
}
/**
* @deprecated as of 1.489
* Use {@link #doStop()}.
*/
@RequirePOST
@Deprecated
public void doStop( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
doStop().generateResponse(req,rsp,this);
}
/**
* Stops the current build.
*
* @since 1.489
*/
@RequirePOST
public HttpResponse doStop() {
lock.writeLock().lock(); // need write lock as interrupt will change the field
try {
if (executable != null) {
Tasks.getOwnerTaskOf(getParentOf(executable)).checkAbortPermission();
interrupt();
}
} finally {
lock.writeLock().unlock();
}
return HttpResponses.forwardToPreviousPage();
}
/**
* @deprecated now a no-op
*/
@Deprecated
public HttpResponse doYank() {
return HttpResponses.redirectViaContextPath("/");
}
/**
* Checks if the current user has a permission to stop this build.
*/
public boolean hasStopPermission() {
lock.readLock().lock();
try {
return executable != null && Tasks.getOwnerTaskOf(getParentOf(executable)).hasAbortPermission();
} finally {
lock.readLock().unlock();
}
}
public @Nonnull Computer getOwner() {
return owner;
}
/**
* Returns when this executor started or should start being idle.
*/
public long getIdleStartMilliseconds() {
lock.readLock().lock();
try {
if (isIdle())
return Math.max(creationTime, owner.getConnectTime());
else {
return Math.max(startTime + Math.max(0, Executables.getEstimatedDurationFor(executable)),
System.currentTimeMillis() + 15000);
}
} finally {
lock.readLock().unlock();
}
}
/**
* Exposes the executor to the remote API.
*/
public Api getApi() {
return new Api(this);
}
/**
* Creates a proxy object that executes the callee in the context that impersonates
* this executor. Useful to export an object to a remote channel.
*/
public <T> T newImpersonatingProxy(Class<T> type, T core) {
return new InterceptingProxy() {
protected Object call(Object o, Method m, Object[] args) throws Throwable {
final Executor old = IMPERSONATION.get();
IMPERSONATION.set(Executor.this);
try {
return m.invoke(o,args);
} finally {
IMPERSONATION.set(old);
}
}
}.wrap(type,core);
}
/**
* Returns the executor of the current thread or null if current thread is not an executor.
*/
public static @CheckForNull Executor currentExecutor() {
Thread t = Thread.currentThread();
if (t instanceof Executor) return (Executor) t;
return IMPERSONATION.get();
}
/**
* Finds the executor currently running a given process.
* @param executable a possibly running executable
* @return the executor (possibly a {@link OneOffExecutor}) whose {@link Executor#getCurrentExecutable} matches that,
* or null if it could not be found (for example because the execution has already completed)
* @since 1.607
*/
@CheckForNull
public static Executor of(Executable executable) {
Jenkins jenkins = Jenkins.getInstanceOrNull(); // TODO confirm safe to assume non-null and use getInstance()
if (jenkins == null) {
return null;
}
for (Computer computer : jenkins.getComputers()) {
for (Executor executor : computer.getExecutors()) {
if (executor.getCurrentExecutable() == executable) {
return executor;
}
}
for (Executor executor : computer.getOneOffExecutors()) {
if (executor.getCurrentExecutable() == executable) {
return executor;
}
}
}
return null;
}
/**
* Returns the estimated duration for the executable.
* Protects against {@link AbstractMethodError}s if the {@link Executable} implementation
* was compiled against Hudson < 1.383
*
* @deprecated as of 1.388
* Use {@link Executables#getEstimatedDurationFor(Queue.Executable)}
*/
@Deprecated
public static long getEstimatedDurationFor(Executable e) {
return Executables.getEstimatedDurationFor(e);
}
/**
* Mechanism to allow threads (in particular the channel request handling threads) to
* run on behalf of {@link Executor}.
*/
private static final ThreadLocal<Executor> IMPERSONATION = new ThreadLocal<Executor>();
private static final Logger LOGGER = Logger.getLogger(Executor.class.getName());
}