-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JENKINS-43653] - Ensure AbstractItem#delete() NPE safety when checking executors #2854
Changes from 3 commits
519bdea
726905c
6fb6158
e2f2fee
23c7437
b69190e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ | |
import hudson.Functions; | ||
import hudson.BulkChange; | ||
import hudson.cli.declarative.CLIResolver; | ||
import hudson.model.Queue.Executable; | ||
import hudson.model.listeners.ItemListener; | ||
import hudson.model.listeners.SaveableListener; | ||
import hudson.model.queue.Tasks; | ||
|
@@ -86,7 +87,9 @@ | |
import javax.xml.transform.stream.StreamResult; | ||
import javax.xml.transform.stream.StreamSource; | ||
|
||
import static hudson.model.queue.Executables.getParentOf; | ||
import static hudson.model.queue.Executables.getParentOfOrFail; | ||
import hudson.model.queue.SubTask; | ||
import java.lang.reflect.InvocationTargetException; | ||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; | ||
import org.apache.commons.io.FileUtils; | ||
import org.kohsuke.accmod.Restricted; | ||
|
@@ -620,9 +623,20 @@ public void delete() throws IOException, InterruptedException { | |
Map<Executor, Queue.Executable> buildsInProgress = new LinkedHashMap<>(); | ||
for (Computer c : Jenkins.getInstance().getComputers()) { | ||
for (Executor e : c.getAllExecutors()) { | ||
WorkUnit workUnit = e.getCurrentWorkUnit(); | ||
if (workUnit != null) { | ||
Item item = Tasks.getItemOf(getParentOf(workUnit.getExecutable())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reported issue is |
||
final WorkUnit workUnit = e.getCurrentWorkUnit(); | ||
final Executable executable = workUnit != null ? workUnit.getExecutable() : null; | ||
SubTask subtask = null; | ||
if (executable != null) { | ||
try { | ||
subtask = getParentOfOrFail(executable); | ||
} catch(InvocationTargetException ex) { | ||
// Executable is not compatible with API changes in 1.377+ | ||
LOGGER.log(Level.WARNING, "Cannot determine subtask for the executable with obsolete API implementation", ex); | ||
} | ||
} | ||
|
||
if (subtask != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be simplified to only check |
||
Item item = Tasks.getItemOf(subtask); | ||
if (item != null) { | ||
while (item != null) { | ||
if (item == this) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,6 +64,7 @@ | |
import java.util.logging.Logger; | ||
|
||
import static hudson.model.queue.Executables.*; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.util.Collection; | ||
import static java.util.logging.Level.*; | ||
import javax.annotation.CheckForNull; | ||
|
@@ -511,6 +512,7 @@ public void completedAsynchronous(@CheckForNull Throwable error) { | |
* null if the executor is idle. | ||
*/ | ||
@Exported | ||
@CheckForNull | ||
public WorkUnit getCurrentWorkUnit() { | ||
lock.readLock().lock(); | ||
try { | ||
|
@@ -843,7 +845,13 @@ public HttpResponse doStop() { | |
lock.writeLock().lock(); // need write lock as interrupt will change the field | ||
try { | ||
if (executable != null) { | ||
Tasks.getOwnerTaskOf(getParentOf(executable)).checkAbortPermission(); | ||
final SubTask parentOf; | ||
try { | ||
parentOf = getParentOfOrFail(executable); | ||
} catch(InvocationTargetException ex) { | ||
return HttpResponses.error(500, ex); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suffices to just throw this exception (or a wrapper) out of the method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a non-Restricted public API (it should be restricted, of course), hence I would prefer to keep the current implementation if you do not feel strongly. The user-visible behavior does not change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
Tasks.getOwnerTaskOf(parentOf).checkAbortPermission(); | ||
interrupt(); | ||
} | ||
} finally { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,17 +29,29 @@ | |
import java.lang.reflect.Method; | ||
import javax.annotation.CheckForNull; | ||
import javax.annotation.Nonnull; | ||
import org.kohsuke.accmod.Restricted; | ||
import org.kohsuke.accmod.restrictions.NoExternalUse; | ||
|
||
/** | ||
* Convenience methods around {@link Executable}. | ||
* | ||
* @author Kohsuke Kawaguchi | ||
*/ | ||
public class Executables { | ||
|
||
// TODO: Deprecate getParentOf() and make the new API public | ||
// @deprecated This method may throw Runtime exceptions for old cores | ||
// Use {@link #getParentOfOrFail(hudson.model.Queue.Executable)} or {@link #getParentOfOrNull(hudson.model.Queue.Executable)} instead. | ||
|
||
/** | ||
* Due to the return type change in {@link Executable}, the caller needs a special precaution now. | ||
* @param e Executable | ||
* @return Discovered subtask | ||
* @throws Error Executable type does not offer the {@link Executable#getParent()} method or it fails with {@link Error} | ||
* @throws RuntimeException {@link Executable#getParent()} method fails with {@link Error} | ||
*/ | ||
public static @Nonnull SubTask getParentOf(Executable e) { | ||
public static @Nonnull SubTask getParentOf(@Nonnull Executable e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the correct thing is to just return |
||
throws Error, RuntimeException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Throws are unnecessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed with other reviewers, these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to declare a method throwing a RuntimeException |
||
try { | ||
return e.getParent(); | ||
} catch (AbstractMethodError _) { | ||
|
@@ -59,6 +71,29 @@ public class Executables { | |
} | ||
} | ||
} | ||
|
||
/** | ||
* Get parent subtask from which the executable has been created. | ||
* @param e Executable | ||
* @return Parent subtask from which the executable has been created | ||
* @throws InvocationTargetException Operation failure due to the usage of incompatible API for old plugin depending on a core older than 1.377 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think we need a new method here. Suffices to document the exception in the original, and have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restricted it for now |
||
* @since TODO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If restricting, delete |
||
*/ | ||
@Nonnull | ||
@Restricted(NoExternalUse.class) | ||
public static SubTask getParentOfOrFail(@Nonnull Executable e) throws InvocationTargetException { | ||
try { | ||
return getParentOf(e); | ||
} catch(RuntimeException | Error ex) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope. It calls the |
||
throw new InvocationTargetException(ex, formatUnsupportedExecutableAPIMessage(e)); | ||
} | ||
} | ||
|
||
@Nonnull | ||
private static String formatUnsupportedExecutableAPIMessage(@Nonnull Executable e) { | ||
return String.format("Cannot retrieve parent subtask of executable %s implementing API version below 1.377 (%s)", | ||
new Object[] {e, e.getClass()}); | ||
} | ||
|
||
/** | ||
* Returns the estimated duration for the executable. | ||
|
@@ -77,6 +112,7 @@ public static long getEstimatedDurationFor(@CheckForNull Executable e) { | |
try { | ||
return e.getEstimatedDuration(); | ||
} catch (AbstractMethodError error) { | ||
// TODO: according to the code above, e.getparent() may fail. The method also needs to be reworked | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noise |
||
return e.getParent().getEstimatedDuration(); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import?