Skip to content

[JEP-222] Add experimental WebSocket support + Jetty update in Winstone + Remoting 4.0 - #1

Open
Chi-Song-Owen wants to merge 298 commits into
masterfrom
websocket
Open

[JEP-222] Add experimental WebSocket support + Jetty update in Winstone + Remoting 4.0#1
Chi-Song-Owen wants to merge 298 commits into
masterfrom
websocket

Conversation

@Chi-Song-Owen

Copy link
Copy Markdown

See JEP-222. Downstream of jenkinsci/winstone#79 + jenkinsci/remoting#357 + jenkinsci/jenkins-test-harness#183.

Proposed Changelog Entries

oleg-nenashev and others added 30 commits November 12, 2019 15:06
[JENKINS-59631] Fix styling issue in build history table
* Try to execute flyweight tasks on the master

* Fix offline behavior, add tests

* Remove obsolete TODOs, also check #isAcceptingTasks
[JENKINS-59793] Avoid hanging jobs with faulty SubTasks
The jenkins-slave.xml file has been copied to the windows-slave-installer-module,
but a unused (old) copy is still present in the jenkins core.
…pane of the Plugin Manager (#4298)

* refs #JENKINS-59665 Fixed sorting for uninstall column of installed in plugin manager

* refs #JENKINS-59665 removed test attribute
* Hide password form fields by default

* Trying to bypass enforced autocompletion by just having a test field at first

Something like onfocus didn't work, you'd tab through form elements
and unless you filled in the user name, changing the form field to
password would cause it to autocomplete.

It looks like, at least in Mac/Firefox, going from plain text to password
in the 'oninput' event handler works. The plain text is revealed neither
with typing nor pasting.

* Update core/src/main/resources/lib/form/password.jelly

Co-Authored-By: daniel-beck <daniel-beck@users.noreply.github.com>

* Use previously defined value

* Make new password form fields opt-out

* Add support for redacting form secrets in new password field

* Have a password value pre-set in Jelly

* Fix method

* Fix test by clicking on the button to change the password

* Forgot period separator between class and property name
[JENKINS-59508] Sidebar links show full label on hover
Switch from maven-jenkins-dev-plugin to upstream jetty-maven-plugin
Add StandardOpenOption.CREATE flag to create FileChannelWriter to avoid full fs flush and 5sec log operation on creating empty file with CephFS as a storage
* JENKINS-6722 Show slave error status on Dashboard

* Update executors.properties
…unds on the plugin side (#3935)

* JENKINS-56553 Make proxy config compatible with JCASC

* Use DataBoundSetters as other fields are optional

* Fix Jelly for ProxyConfiguration

* Use default field name now that field matches

* Fix validate proxy

* Change field type

* After review

* Update core/src/main/java/jenkins/model/Jenkins.java

Co-Authored-By: timja <t.jacomb@kainos.com>

* Update core/src/main/java/jenkins/model/Jenkins.java

Co-Authored-By: timja <t.jacomb@kainos.com>

* Update core/src/main/java/hudson/ProxyConfiguration.java

Co-Authored-By: timja <t.jacomb@kainos.com>

* Update core/src/main/java/jenkins/model/Jenkins.java

Co-Authored-By: timja <t.jacomb@kainos.com>

* Update core/src/main/java/jenkins/model/Jenkins.java

Co-Authored-By: timja <t.jacomb@kainos.com>

* Update core/src/main/java/hudson/ProxyConfiguration.java

Co-Authored-By: Oleg Nenashev <o.v.nenashev@gmail.com>

* Tweak javadoc

* Align fields

* Update core/src/main/java/hudson/ProxyConfiguration.java

Co-Authored-By: Adrien Lecharpentier <adrien.lecharpentier@gmail.com>
Document workaround for Stapler data binding bug in repeatableProperty
…ling events (#4345)

* Rejuvenate log levels

* clean up

* Reverting the plugin download

Co-Authored-By: Oleg Nenashev <o.v.nenashev@gmail.com>
jsoref and others added 29 commits January 10, 2020 12:26
… Fix JavaScript error in Plugin Manager when optional dependency metadata cannot be retrieved (#3904)

* [JENKINS-56173] Uncaught TypeError: Cannot read property 'jenkinsPluginMetadata' of undefined

* [JENKINS-56173] missing dependencies covers both not installed and not enabled

* [JENKINS-56152] i18n: DirectlyDependentPlugins.title / IndirectlyDependentPlugins.title

* [JENKINS-56152] replace `version X` with `(X)`

* [JENKINS-56152] simplify messages; replace `version X` with `(X)`; use higher instead of later

* [JENKINS-56173] Uncaught Exception: TypeError: Cannot read property 'get' of undefined at _createResponder

* Update PluginWrapperTest

* Switch to descriptive class for plugin version
[JENKINS-42658] - Fix NPE in Agent API when the agent is offline (e.g. retrieving agent version or OS description)
Inlining test-pom into test and removing test-jdk8
…IZABLE, ES_COMPARING_STRINGS_WITH_EQ) (#4379)

* fixed Spotbugs issues (SE_COMPARATOR_SHOULD_BE_SERIALIZABLE, ES_COMPARING_STRINGS_WITH_EQ)

* replaced wildcard import with specific imports

* added missing import

* fixed spotbugs issues

* removed accidently added import
…stages (#4207)

* [JENKINS-59412] Clarify that build history does not include pipeline stages

* Clarify that pipelines are merely an example of tasks that may not be listed
Simplify CoreUpdateMonitor/message.properties: changelog.url → jenkins-version.properties
* Add the maintainer checklist to the pull request template

* Add checklist items for LTS

* Add "Proposed changelog entries" to the pull request template

* Pull request template: `Internal: ` prefix is now added automatically by the Core Changelog generator

jenkinsci/core-changelog-generator#6

* Apply suggestions from review by @MarkEWaite

Co-Authored-By: Mark Waite <mark.earl.waite@gmail.com>

Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
Upload draft Changelog YAMLs to GitHub Release assets
…or-yaml

Revert "Upload draft Changelog YAMLs to GitHub Release assets"
[JENKINS-60740] - Rely on GitHub Actions to generate the release draft YAMLs
Release Drafter Fix: Properly inherit the parent config
[JENKINS-60716] Retain causes of a LogRotator failure for diagnosis
* Spotbugs: Fixed comparing strings with equals instead of ==

* Spotbugs: Do not return null in @nonnull marked method, instead return empty string.

* Added Suppress for Spotbugs

* Apply suggestions from code review

Co-Authored-By: Oleg Nenashev <o.v.nenashev@gmail.com>

* fixed javadoc

Co-authored-by: Oleg Nenashev <o.v.nenashev@gmail.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces WebSocket support for inbound agents and the CLI (JEP-222), separates cloud configuration into a standalone page, modernizes class loading and exception handling, and removes legacy JNLP protocols. The review feedback highlights several critical and high-severity issues, including a bug in Slave.java that causes 404 errors when downloading the CLI jar, potential NullPointerExceptions in finally blocks within WorkUnitContext that could swallow primary exceptions, a potential ConcurrentModificationException in UpdateCenter's background thread, and a missing frame size limit in PlainCLIProtocol that could lead to OutOfMemory errors. Additionally, the reviewer recommends marking shared fields as volatile in WebSocketSession, replacing printStackTrace() with proper logging, and adding defensive null checks across several components.

Comment on lines +418 to +420
File jar = Which.jarFile(owner);
if (jar.isFile()) {
name = "lib/" + jar.getName();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This refactoring introduces a critical bug for jenkins-cli.jar. If cliJar.isFile() is true, name is changed to lib/cli-<version>.jar. However, the webapp only serves the CLI jar under /jnlpJars/jenkins-cli.jar. Changing the name to lib/cli-<version>.jar will cause servletContext.getResource to return null, resulting in a 404 error when users try to download the CLI jar. Keep the name as jenkins-cli.jar for CLI.class.

Suggested change
File jar = Which.jarFile(owner);
if (jar.isFile()) {
name = "lib/" + jar.getName();
File jar = Which.jarFile(owner);
if (jar.isFile()) {
if (owner == CLI.class) {
name = "jenkins-cli.jar";
} else {
name = "lib/" + jar.getName();
}
} else {

Comment on lines +133 to +135
if (framelen < 0) {
throw new IOException("corrupt stream: negative frame length");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To prevent integer overflow and potential OutOfMemoryError (DoS) when reading very large frame lengths, add a check to limit the maximum allowed frame size (e.g., 100MB).

                    if (framelen < 0) {
                        throw new IOException("corrupt stream: negative frame length");
                    }
                    if (framelen > 100 * 1024 * 1024) {
                        throw new IOException("Frame length " + framelen + " exceeds maximum allowed size");
                    }

Comment on lines +119 to +125
Computer.threadPoolForRemoting.submit(() -> {
LOGGER.fine(() -> "setting up channel for " + agent);
state.fireBeforeChannel(new ChannelBuilder(agent, Computer.threadPoolForRemoting));
state.fireAfterChannel(state.getChannelBuilder().build(new Transport()));
LOGGER.fine(() -> "set up channel for " + agent);
return null;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If an exception is thrown during channel building or setup (e.g., IOException from build), it will be swallowed by the thread pool because the Future is not checked. Wrap the setup logic in a try-catch block to log the failure and close the session properly.

Suggested change
Computer.threadPoolForRemoting.submit(() -> {
LOGGER.fine(() -> "setting up channel for " + agent);
state.fireBeforeChannel(new ChannelBuilder(agent, Computer.threadPoolForRemoting));
state.fireAfterChannel(state.getChannelBuilder().build(new Transport()));
LOGGER.fine(() -> "set up channel for " + agent);
return null;
});
Computer.threadPoolForRemoting.submit(() -> {
try {
LOGGER.fine(() -> "setting up channel for " + agent);
state.fireBeforeChannel(new ChannelBuilder(agent, Computer.threadPoolForRemoting));
state.fireAfterChannel(state.getChannelBuilder().build(new Transport()));
LOGGER.fine(() -> "set up channel for " + agent);
} catch (Exception x) {
LOGGER.log(Level.WARNING, "failed to set up channel for " + agent, x);
close();
}
return null;
});

Comment on lines +62 to +64
private Object session;
private Object remoteEndpoint;
private ScheduledFuture<?> pings;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The fields session, remoteEndpoint, and pings are accessed and modified by different threads (e.g., during connection, close, and scheduled ping tasks). They should be marked as volatile to ensure thread visibility and prevent potential resource leaks where a ping task continues running after the session is closed.

Suggested change
private Object session;
private Object remoteEndpoint;
private ScheduledFuture<?> pings;
private volatile Object session;
private volatile Object remoteEndpoint;
private volatile ScheduledFuture<?> pings;

Comment on lines +164 to +167
} catch (Exception x) {
x.printStackTrace();
throw new IOException(x);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using x.printStackTrace() as it prints directly to standard error and bypasses the logging framework. Use LOGGER.log(Level.WARNING, ...) or simply wrap and throw the exception.

                } catch (Exception x) {
                    throw new IOException(x);
                }

Comment on lines +120 to +124
Executor e = Executor.currentExecutor();
WorkUnit wu = e.getCurrentWorkUnit();
if (wu.isMainWork()) {
future.start.set(e.getCurrentExecutable());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If Executor.currentExecutor() or e.getCurrentWorkUnit() returns null, a NullPointerException will be thrown in the finally block. This will suppress/swallow the primary InterruptedException thrown by startLatch.synchronize(), making debugging extremely difficult. Add defensive null checks.

            Executor e = Executor.currentExecutor();
            if (e != null) {
                WorkUnit wu = e.getCurrentWorkUnit();
                if (wu != null && wu.isMainWork()) {
                    future.start.set(e.getCurrentExecutable());
                }
            }

Comment on lines +148 to 157
WorkUnit wu = e.getCurrentWorkUnit();
if (wu.isMainWork()) {
if (problems == null) {
future.set(executable);
e.getOwner().taskCompleted(e, task, duration);
} else {
future.set(problems);
e.getOwner().taskCompletedWithProblems(e, task, duration, problems);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensively check if wu is null before calling wu.isMainWork() to prevent a NullPointerException in the finally block, which would swallow any primary exception or interruption.

Suggested change
WorkUnit wu = e.getCurrentWorkUnit();
if (wu.isMainWork()) {
if (problems == null) {
future.set(executable);
e.getOwner().taskCompleted(e, task, duration);
} else {
future.set(problems);
e.getOwner().taskCompletedWithProblems(e, task, duration, problems);
}
}
WorkUnit wu = e.getCurrentWorkUnit();
if (wu != null && wu.isMainWork()) {
if (problems == null) {
future.set(executable);
e.getOwner().taskCompleted(e, task, duration);
} else {
future.set(problems);
e.getOwner().taskCompletedWithProblems(e, task, duration, problems);
}
}

Comment on lines +556 to +559
Timer.get().submit(() -> {
sites.forEach(UpdateSite::getData);
siteDataLoading = false;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Iterating over sites (which is a PersistedList) in a background thread can throw a ConcurrentModificationException if another thread modifies the list of update sites concurrently. Create a copy of the list before submitting the task to the executor.

                List<UpdateSite> targets = new ArrayList<>(sites);
                Timer.get().submit(() -> {
                    targets.forEach(UpdateSite::getData);
                    siteDataLoading = false;
                });

Comment on lines +234 to +237
if (depPluginTR) {
var depPluginMetadata = depPluginTR.jenkinsPluginMetadata;
enabled = depPluginMetadata.enableInput.checked;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensively check if depPluginTR.jenkinsPluginMetadata and enableInput are defined before accessing checked to prevent potential JavaScript errors on the plugin manager page.

                    if (depPluginTR && depPluginTR.jenkinsPluginMetadata && depPluginTR.jenkinsPluginMetadata.enableInput) {
                        enabled = depPluginTR.jenkinsPluginMetadata.enableInput.checked;
                    }

public CompositeIOException(String message, @Nonnull List<IOException> exceptions) {
super(message);
this.exceptions = exceptions;
exceptions.forEach(this::addSuppressed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Defensively check for null elements in the exceptions list before calling addSuppressed to prevent a NullPointerException.

Suggested change
exceptions.forEach(this::addSuppressed);
exceptions.forEach(ex -> {
if (ex != null) {
addSuppressed(ex);
}
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.