- * We look for a setting that affects the smallest scope first, then bigger ones later. + *
We look for a setting that affects the smallest scope first, then bigger ones later. * - *
- * People makes configuration mistakes, so we are trying to be nice + *
People make configuration mistakes, so we are trying to be nice * with those by doing {@link String#trim()}. * - *
* @return the File alongside with some description to help the user troubleshoot issues */ public FileAndDescription getHomeDir(ServletContextEvent event) { diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java index 8e84179b5a0fb..92abad4cb6326 100644 --- a/core/src/main/java/hudson/lifecycle/Lifecycle.java +++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java @@ -24,6 +24,8 @@ package hudson.lifecycle; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionPoint; import hudson.Functions; import hudson.Util; @@ -32,6 +34,7 @@ import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; @@ -52,6 +55,19 @@ public abstract class Lifecycle implements ExtensionPoint { private static Lifecycle INSTANCE = null; + public Lifecycle() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + Jenkins jenkins = Jenkins.getInstanceOrNull(); + if (jenkins != null) { + try { + jenkins.cleanUp(); + } catch (Throwable t) { + LOGGER.log(Level.SEVERE, "Failed to clean up. Shutdown will continue.", t); + } + } + })); + } + /** * Gets the singleton instance. * @@ -107,6 +123,9 @@ public void verifyRestartable() throws RestartNotSupportedException { } else if (System.getenv("SMF_FMRI") != null && System.getenv("SMF_RESTARTER") != null) { // when we are run by Solaris SMF, these environment variables are set. instance = new SolarisSMFLifecycle(); + } else if (System.getenv("NOTIFY_SOCKET") != null) { + // When we are running under systemd with Type=notify, this environment variable is set. + instance = new SystemdLifecycle(); } else { // if run on Unix, we can do restart try { @@ -232,5 +251,74 @@ public boolean canRestart() { } } + /** + * Called when Jenkins startup is finished or when Jenkins has finished reloading its + * configuration. + * + * @since 2.333 + */ + public void onReady() { + LOGGER.log(Level.INFO, "Jenkins is fully up and running"); + } + + /** + * Called when Jenkins is reloading its configuration. + * + *
Callers must also send an {@link #onReady()} notification when Jenkins has finished + * reloading its configuration. + * + * @since 2.333 + */ + public void onReload(@NonNull String user, @CheckForNull String remoteAddr) { + if (remoteAddr != null) { + LOGGER.log( + Level.INFO, + "Reloading Jenkins as requested by {0} from {1}", + new Object[] {user, remoteAddr}); + } else { + LOGGER.log(Level.INFO, "Reloading Jenkins as requested by {0}", user); + } + } + + /** + * Called when Jenkins is beginning its shutdown. + * + * @since 2.333 + */ + public void onStop(@NonNull String user, @CheckForNull String remoteAddr) { + if (remoteAddr != null) { + LOGGER.log( + Level.INFO, + "Stopping Jenkins as requested by {0} from {1}", + new Object[] {user, remoteAddr}); + } else { + LOGGER.log(Level.INFO, "Stopping Jenkins as requested by {0}", user); + } + } + + /** + * Tell the service manager to extend the startup or shutdown timeout. The value specified is a + * time during which either {@link #onExtendTimeout(long, TimeUnit)} must be called again or + * startup/shutdown must complete. + * + * @param timeout The amount by which to extend the timeout. + * @param unit The time unit of the timeout argument. + * + * @since TODO + */ + public void onExtendTimeout(long timeout, @NonNull TimeUnit unit) {} + + /** + * Called when Jenkins service state has changed. + * + * @param status The status string. This is free-form and can be used for various purposes: + * general state feedback, completion percentages, human-readable error message, etc. + * + * @since 2.333 + */ + public void onStatusUpdate(String status) { + LOGGER.log(Level.INFO, status); + } + private static final Logger LOGGER = Logger.getLogger(Lifecycle.class.getName()); } diff --git a/core/src/main/java/hudson/lifecycle/SystemdLifecycle.java b/core/src/main/java/hudson/lifecycle/SystemdLifecycle.java new file mode 100644 index 0000000000000..31afd55ccf60a --- /dev/null +++ b/core/src/main/java/hudson/lifecycle/SystemdLifecycle.java @@ -0,0 +1,69 @@ +package hudson.lifecycle; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Library; +import com.sun.jna.Native; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * {@link Lifecycle} that delegates its responsibility to {@code systemd(1)}. + * + * @author Basil Crow + */ +@Restricted(NoExternalUse.class) +@Extension(optional = true) +public class SystemdLifecycle extends ExitLifecycle { + + private static final Logger LOGGER = Logger.getLogger(SystemdLifecycle.class.getName()); + + interface Systemd extends Library { + Systemd INSTANCE = Native.load("systemd", Systemd.class); + + int sd_notify(int unset_environment, String state) throws LastErrorException; + } + + @Override + public void onReady() { + super.onReady(); + notify("READY=1"); + } + + @Override + public void onReload(@NonNull String user, @CheckForNull String remoteAddr) { + super.onReload(user, remoteAddr); + notify("RELOADING=1"); + } + + @Override + public void onStop(@NonNull String user, @CheckForNull String remoteAddr) { + super.onStop(user, remoteAddr); + notify("STOPPING=1"); + } + + @Override + public void onExtendTimeout(long timeout, @NonNull TimeUnit unit) { + super.onExtendTimeout(timeout, unit); + notify(String.format("EXTEND_TIMEOUT_USEC=%d", unit.toMicros(timeout))); + } + + @Override + public void onStatusUpdate(String status) { + super.onStatusUpdate(status); + notify(String.format("STATUS=%s", status)); + } + + private static synchronized void notify(String message) { + try { + Systemd.INSTANCE.sd_notify(0, message); + } catch (LastErrorException e) { + LOGGER.log(Level.WARNING, null, e); + } + } +} diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 458718e89aff7..c60530639cbfe 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -527,9 +527,6 @@ public void onLoad(ItemGroup extends Item> parent, String name) throws IOExcep * then it will be loaded, then this method will be invoked * to perform any implementation-specific work. * - *
- * - * * @param src * Item from which it's copied from. The same type as {@code this}. Never null. */ diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index e0abed68ccd5e..50ad97232bd5a 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -1919,9 +1919,8 @@ default CauseOfBlockage getCauseOfBlockage() { * amongst all the free executors on all possibly suitable nodes. * NOTE: To be able to re-use the same node during the next run this key should not change from one run to * another. You probably want to compute that key based on the job's name. - *
- * @return by default: {@link #getFullDisplayName()} * + * @return by default: {@link #getFullDisplayName()} * @see hudson.model.LoadBalancer */ default String getAffinityKey() { return getFullDisplayName(); } diff --git a/core/src/main/java/hudson/scheduler/Hash.java b/core/src/main/java/hudson/scheduler/Hash.java index eb69cfda04fb5..86520ed87faae 100644 --- a/core/src/main/java/hudson/scheduler/Hash.java +++ b/core/src/main/java/hudson/scheduler/Hash.java @@ -33,16 +33,12 @@ /** * Generates a pseudo-random sequence of integers in the specified range. * - *
- * {@link CronTab} supports tokens like '@daily', which means "do it once a day". + *
{@link CronTab} supports tokens like '@daily', which means "do it once a day". * Exactly which time of the day this gets scheduled is randomized --- randomized * in the sense that it's spread out when many jobs choose @daily, but it's at * the same time stable so that every job sticks to a specific time of the day * even after the configuration is updated. * - *
- * - * * @author Kohsuke Kawaguchi * @since 1.448 */ diff --git a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java index 924c77e25cd5b..dd29dc3004fea 100644 --- a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java +++ b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java @@ -67,7 +67,7 @@ * This causes the container to perform authentication, but there's no way * to find out whether the user has been successfully authenticated or not. * So to find this out, we then redirect the user to - * {@link jenkins.model.Jenkins#doSecured(StaplerRequest, StaplerResponse) {@code /secured/...} page}. + * {@link jenkins.model.Jenkins#doSecured(StaplerRequest, StaplerResponse) /secured/... page}. * *
* The handler of the above URL checks if the user is authenticated,
diff --git a/core/src/main/java/hudson/util/LogTaskListener.java b/core/src/main/java/hudson/util/LogTaskListener.java
index 7d696ada46323..267f8f7bf27fc 100644
--- a/core/src/main/java/hudson/util/LogTaskListener.java
+++ b/core/src/main/java/hudson/util/LogTaskListener.java
@@ -37,12 +37,10 @@
import java.util.logging.LogRecord;
import java.util.logging.Logger;
-// TODO: AbstractTaskListener is empty now, but there are dependencies on that e.g. Ruby Runtime - JENKINS-48116)
-// The change needs API deprecation policy or external usages cleanup.
-
/**
* {@link TaskListener} which sends messages to a {@link Logger}.
*/
+@SuppressWarnings("deprecation") // to preserve serial form
public class LogTaskListener extends AbstractTaskListener implements TaskListener, Closeable {
// would be simpler to delegate to the LogOutputStream but this would incompatibly change the serial form
diff --git a/core/src/main/java/hudson/util/StreamTaskListener.java b/core/src/main/java/hudson/util/StreamTaskListener.java
index 8aad40f5b5999..f19db94b45cd4 100644
--- a/core/src/main/java/hudson/util/StreamTaskListener.java
+++ b/core/src/main/java/hudson/util/StreamTaskListener.java
@@ -48,9 +48,6 @@
import jenkins.util.SystemProperties;
import org.kohsuke.stapler.framework.io.WriterOutputStream;
-// TODO: AbstractTaskListener is empty now, but there are dependencies on that e.g. Ruby Runtime - JENKINS-48116)
-// The change needs API deprecation policy or external usages cleanup.
-
/**
* {@link TaskListener} that generates output into a single stream.
*
@@ -59,6 +56,7 @@
*
* @author Kohsuke Kawaguchi
*/
+@SuppressWarnings("deprecation") // to preserve serial form
public class StreamTaskListener extends AbstractTaskListener implements TaskListener, Closeable {
@NonNull
private PrintStream out;
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index 2ac02f6062b6e..204fd0e39d851 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -81,7 +81,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.regex.Pattern;
import jenkins.model.Jenkins;
import jenkins.util.xstream.SafeURLConverter;
@@ -570,18 +569,12 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
throw new ConversionException("Refusing to unmarshal " + reader.getNodeName() + " for security reasons; see https://www.jenkins.io/redirect/class-filter/");
}
- /** TODO see comment in {@code whitelisted-classes.txt} */
- private static final Pattern JRUBY_PROXY = Pattern.compile("org[.]jruby[.]proxy[.].+[$]Proxy\\d+");
-
@Override
public boolean canConvert(Class type) {
if (type == null) {
return false;
}
String name = type.getName();
- if (JRUBY_PROXY.matcher(name).matches()) {
- return false;
- }
// claim we can convert all the scary stuff so we can throw exceptions when attempting to do so
return ClassFilter.DEFAULT.isBlacklisted(name) || ClassFilter.DEFAULT.isBlacklisted(type);
}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index a9323a48bf700..5e6b074be66cb 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -1181,6 +1181,7 @@ private boolean containsLinkageError(Throwable x) {
@Override
protected void onInitMilestoneAttained(InitMilestone milestone) {
initLevel = milestone;
+ getLifecycle().onExtendTimeout(EXTEND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (milestone == PLUGINS_PREPARED) {
// set up Guice to enable injection as early as possible
// before this milestone, ExtensionList.ensureLoaded() won't actually try to locate instances
@@ -2268,7 +2269,7 @@ private void trimLabels(@CheckForNull Set