Skip to content

Commit

Permalink
Merge pull request #7650 from NotMyFault/backporting-2.387.1
Browse files Browse the repository at this point in the history
Backporting for 2.387.1
  • Loading branch information
NotMyFault authored Feb 15, 2023
2 parents 52786df + 347732c commit bf418c7
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 187 deletions.
161 changes: 32 additions & 129 deletions core/src/main/java/hudson/TcpSlaveAgentListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.AperiodicWork;
import hudson.slaves.OfflineCause;
import hudson.util.VersionNumber;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -81,7 +80,7 @@
@StaplerAccessibleType
public final class TcpSlaveAgentListener extends Thread {

private final ServerSocketChannel serverSocket;
private ServerSocketChannel serverSocket;
private volatile boolean shuttingDown;

public final int configuredPort;
Expand All @@ -92,24 +91,27 @@ public final class TcpSlaveAgentListener extends Thread {
*/
public TcpSlaveAgentListener(int port) throws IOException {
super("TCP agent listener port=" + port);
try {
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
} catch (BindException e) {
throw (BindException) new BindException("Failed to listen on port " + port + " because it's already in use.").initCause(e);
}
serverSocket = createSocket(port);
this.configuredPort = port;
setUncaughtExceptionHandler((t, e) -> {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener " + t + ", attempting to reschedule thread", e);
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener " + t, e);
shutdown();
TcpSlaveAgentListenerRescheduler.schedule(t, e);
});

LOGGER.log(Level.FINE, "TCP agent listener started on port {0}", getPort());

start();
}

private static ServerSocketChannel createSocket(int port) throws IOException {
ServerSocketChannel result;
try {
result = ServerSocketChannel.open();
result.socket().bind(new InetSocketAddress(port));
} catch (BindException e) {
throw (BindException) new BindException("Failed to listen on port " + port + " because it's already in use.").initCause(e);
}
return result;
}

/**
* Gets the TCP port number in which we are listening.
*/
Expand Down Expand Up @@ -172,9 +174,9 @@ public VersionNumber getRemotingMinimumVersion() {

@Override
public void run() {
try {
// the loop eventually terminates when the socket is closed.
while (!shuttingDown) {
// the loop eventually terminates when the thread shuts down
while (!shuttingDown) {
try {
Socket s = serverSocket.accept().socket();

// this prevents a connection from silently terminated by the router in between or the other peer
Expand All @@ -184,18 +186,20 @@ public void run() {
// we take care of buffering on our own
s.setTcpNoDelay(true);

new ConnectionHandler(s, new ConnectionHandlerFailureCallback(this) {
@Override
public void run(Throwable cause) {
LOGGER.log(Level.WARNING, "Connection handler failed, restarting listener", cause);
shutdown();
TcpSlaveAgentListenerRescheduler.schedule(getParentThread(), cause);
new ConnectionHandler(s).start();
} catch (Throwable e) {
if (!shuttingDown) {
LOGGER.log(Level.SEVERE, "Failed to accept TCP connections", e);
if (!serverSocket.isOpen()) {
LOGGER.log(Level.INFO, "Restarting server socket");
try {
serverSocket = createSocket(configuredPort);
LOGGER.log(Level.INFO, "TCP agent listener restarted on port {0}", getPort());
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, "Failed to restart server socket", ioe);
}
}
}).start();
}
} catch (IOException e) {
if (!shuttingDown) {
LOGGER.log(Level.SEVERE, "Failed to accept TCP connections", e);
}
}
}
}
Expand Down Expand Up @@ -234,21 +238,12 @@ private final class ConnectionHandler extends Thread {
*/
private final int id;

ConnectionHandler(Socket s, ConnectionHandlerFailureCallback parentTerminator) {
ConnectionHandler(Socket s) {
this.s = s;
synchronized (getClass()) {
id = iotaGen++;
}
setName("TCP agent connection handler #" + id + " with " + s.getRemoteSocketAddress());
setUncaughtExceptionHandler((t, e) -> {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener ConnectionHandler " + t, e);
try {
s.close();
parentTerminator.run(e);
} catch (IOException e1) {
LOGGER.log(Level.WARNING, "Could not close socket after unexpected thread death", e1);
}
});
}

@Override
Expand Down Expand Up @@ -295,7 +290,7 @@ public void run() {
} catch (IOException ex) {
// try to clean up the socket
}
} catch (IOException e) {
} catch (Throwable e) {
if (e instanceof EOFException) {
LOGGER.log(Level.INFO, () -> "Connection " + connectionInfo + " failed: " + e.getMessage());
} else {
Expand Down Expand Up @@ -351,21 +346,6 @@ private void error(String msg, Socket s) throws IOException {
}
}

// This is essentially just to be able to pass the parent thread into the callback, as it can't access it otherwise
private abstract static class ConnectionHandlerFailureCallback {
private Thread parentThread;

ConnectionHandlerFailureCallback(Thread parentThread) {
this.parentThread = parentThread;
}

public Thread getParentThread() {
return parentThread;
}

public abstract void run(Throwable cause);
}

/**
* This extension provides a Ping protocol that allows people to verify that the {@link TcpSlaveAgentListener} is alive.
* We also use this to wake the acceptor thread on termination.
Expand Down Expand Up @@ -436,83 +416,6 @@ public boolean connect(Socket socket) throws IOException {
}
}

/**
* Reschedules the {@code TcpSlaveAgentListener} on demand. Disables itself after running.
*/
@Extension
@Restricted(NoExternalUse.class)
public static class TcpSlaveAgentListenerRescheduler extends AperiodicWork {
private Thread originThread;
private Throwable cause;
private long recurrencePeriod = 5000;
private boolean isActive;

public TcpSlaveAgentListenerRescheduler() {
isActive = false;
}

public TcpSlaveAgentListenerRescheduler(Thread originThread, Throwable cause) {
this.originThread = originThread;
this.cause = cause;
this.isActive = false;
}

public void setOriginThread(Thread originThread) {
this.originThread = originThread;
}

public void setCause(Throwable cause) {
this.cause = cause;
}

public void setActive(boolean active) {
isActive = active;
}

@Override
public long getRecurrencePeriod() {
return recurrencePeriod;
}

@Override
public AperiodicWork getNewInstance() {
return new TcpSlaveAgentListenerRescheduler(originThread, cause);
}

@Override
protected void doAperiodicRun() {
if (isActive) {
try {
if (originThread.isAlive()) {
originThread.interrupt();
}
int port = Jenkins.get().getSlaveAgentPort();
if (port != -1) {
new TcpSlaveAgentListener(port).start();
LOGGER.log(Level.INFO, "Restarted TcpSlaveAgentListener");
} else {
LOGGER.log(Level.SEVERE, "Uncaught exception in TcpSlaveAgentListener " + originThread + ". Port is disabled, not rescheduling", cause);
}
isActive = false;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Could not reschedule TcpSlaveAgentListener - trying again.", cause);
}
}
}

public static void schedule(Thread originThread, Throwable cause) {
schedule(originThread, cause, 5000);
}

public static void schedule(Thread originThread, Throwable cause, long approxDelay) {
TcpSlaveAgentListenerRescheduler rescheduler = AperiodicWork.all().get(TcpSlaveAgentListenerRescheduler.class);
rescheduler.originThread = originThread;
rescheduler.cause = cause;
rescheduler.recurrencePeriod = approxDelay;
rescheduler.isActive = true;
}
}


/**
* Connection terminated because we are reconnected from the current peer.
Expand Down
17 changes: 13 additions & 4 deletions core/src/main/java/jenkins/agents/WebSocketAgents.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.slaves.JnlpAgentReceiver;
Expand Down Expand Up @@ -166,11 +167,19 @@ protected void error(Throwable cause) {

class Transport extends AbstractByteBufferCommandTransport {

Transport() {
super(true);
}

@Override
protected void write(ByteBuffer header, ByteBuffer data) throws IOException {
LOGGER.finest(() -> "sending message of length " + ChunkHeader.length(ChunkHeader.peek(header)));
sendBinary(header, false);
sendBinary(data, true);
protected void write(ByteBuffer headerAndData) throws IOException {
// As in Engine.runWebSocket:
LOGGER.finest(() -> "sending message of length " + (headerAndData.remaining() - ChunkHeader.SIZE));
try {
sendBinary(headerAndData).get(5, TimeUnit.MINUTES);
} catch (Exception x) {
throw new IOException(x);
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/telemetry/Telemetry.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ protected void execute(TaskListener listener) throws IOException, InterruptedExc
return;
}

JSONObject data = new JSONObject();
JSONObject data = null;
try {
data = telemetry.createContent();
} catch (RuntimeException e) {
Expand Down
6 changes: 0 additions & 6 deletions core/src/main/resources/hudson/model/Computer/index-top.jelly
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:t="/lib/hudson">
<!-- Node name and status -->
<h1>
<l:icon src="${it.iconClassName}" class="icon-xlg" />
${it.caption}
</h1>

<!-- Node description -->
<t:editableDescription permission="${it.CONFIGURE}" description="${it.node.nodeDescription}" submissionUrl="submitDescription" />
</j:jelly>
21 changes: 12 additions & 9 deletions core/src/main/resources/hudson/model/Computer/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,34 @@ THE SOFTWARE.
<st:include page="sidepanel.jelly" />
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
<l:main-panel>
<st:include page="index-top.jelly" it="${it}" />

<!-- temporarily offline switch -->
<div style="float:right">
<l:app-bar title="${it.caption}">
<j:if test="${it.manualLaunchAllowed}">
<st:include from="${it.launcher}" page="app-bar-controls.jelly" optional="true"/>
</j:if>
<!-- temporarily offline switch -->
<j:choose>
<j:when test="${it.temporarilyOffline}">
<l:hasPermission permission="${it.CONNECT}">
<form method="post" action="toggleOffline">
<f:submit value="${%submit.temporarilyOffline}" />
</form>
<br/>
<form method="post" action="setOfflineCause">
<f:submit value="${%submit.updateOfflineCause}" />
<f:submit primary="false" value="${%submit.updateOfflineCause}" />
</form>
</l:hasPermission>
</j:when>
<j:otherwise>
<l:hasPermission permission="${it.DISCONNECT}">
<form method="post" action="markOffline">
<f:submit value="${%submit.not.temporarilyOffline}" />
<f:submit primary="false" value="${%submit.not.temporarilyOffline}" />
</form>
</l:hasPermission>
</j:otherwise>
</j:choose>
</div>
<t:help href="https://www.jenkins.io/doc/book/using/using-agents/" />
</l:app-bar>

<st:include page="index-top.jelly" it="${it}" />

<j:if test="${it.offlineCause!=null and it.offline and !it.connecting}">
<st:include it="${it.offlineCause}" page="cause.jelly" />
Expand All @@ -67,7 +70,7 @@ THE SOFTWARE.
</j:if>

<j:if test="${it.node.assignedLabels.size() gt 1}">
<div>
<div class="jenkins-!-margin-bottom-3">
<h2>${%Labels}</h2>
<j:forEach var="entry" items="${it.node.labelCloud}">
<!-- Skip the label for this node -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ THE SOFTWARE.
<l:header />
<l:side-panel>
<l:tasks>
<l:task contextMenu="false" href="${rootURL}/${it.url}" icon="symbol-computer" title="${%Status}"/>
<l:task contextMenu="false" href="${rootURL}/${it.url}" icon="${it.iconClassName}" title="${%Status}"/>
<l:task href="${rootURL}/${it.url}delete" icon="icon-edit-delete icon-md" permission="${it.DELETE}" title="${%Delete Agent}"/>
<l:task href="${rootURL}/${it.url}configure" icon="symbol-settings" permission="${it.EXTENDED_READ}"
title="${it.hasPermission(it.CONFIGURE) ? '%Configure' : '%View Configuration'}"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!--
The MIT License
Copyright (c) 2023, Jenkins project contributors
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.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form">
<j:if test="${it.launchSupported and it.offline and !it.temporarilyOffline}">
<j:choose>
<j:when test="${it.isConnecting()}">
<l:hasPermission permission="${it.CONNECT}">
<form method="post" action="launchSlaveAgent">
<f:submit value="${%Relaunch agent}"/>
</form>
</l:hasPermission>
</j:when>
<j:otherwise>
<l:hasPermission permission="${it.CONNECT}">
<form method="post" action="launchSlaveAgent">
<f:submit value="${%Launch agent}"/>
</form>
</l:hasPermission>
</j:otherwise>
</j:choose>
</j:if>
</j:jelly>
Loading

0 comments on commit bf418c7

Please sign in to comment.