Skip to content

Commit

Permalink
[FIXED JENKINS-33319] Premtively wake the acceptor thread to let it c…
Browse files Browse the repository at this point in the history
…lose cleanly

- Also adds a Ping Agent protocol that could be used by nagios monitoring, etc. to verify that the slave agent listener is alive
- We use the ping agent protocol to ensure that the acceptor thread wakes up, loops and sees that the shutdown is started
  that prevents the socket close exception from being thrown
  • Loading branch information
stephenc committed Mar 4, 2016
1 parent 0cace71 commit 2a61f30
Showing 1 changed file with 92 additions and 3 deletions.
95 changes: 92 additions & 3 deletions core/src/main/java/hudson/TcpSlaveAgentListener.java
Expand Up @@ -24,6 +24,12 @@
package hudson;

import hudson.slaves.OfflineCause;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketAddress;
import java.util.Arrays;
import jenkins.AgentProtocol;

import java.io.BufferedWriter;
Expand All @@ -37,6 +43,7 @@
import java.nio.channels.ServerSocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;

/**
* Listens to incoming TCP connections from JNLP slave agents and CLI.
Expand Down Expand Up @@ -91,7 +98,7 @@ public int getPort() {
public void run() {
try {
// the loop eventually terminates when the socket is closed.
while (true) {
while (!shuttingDown) {
Socket s = serverSocket.accept().socket();

// this prevents a connection from silently terminated by the router in between or the other peer
Expand All @@ -115,6 +122,16 @@ public void run() {
*/
public void shutdown() {
shuttingDown = true;
try {
SocketAddress localAddress = serverSocket.getLocalAddress();
if (localAddress instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) localAddress;
Socket client = new Socket(address.getHostName(), address.getPort());
new PingAgentProtocol().connect(client);
}
} catch (IOException e) {
LOGGER.log(Level.FINE, "Failed to send Ping to wake acceptor loop", e);
}
try {
serverSocket.close();
} catch (IOException e) {
Expand All @@ -140,7 +157,7 @@ public ConnectionHandler(Socket s) {
@Override
public void run() {
try {
LOGGER.info("Accepted connection #"+id+" from "+s.getRemoteSocketAddress());
LOGGER.log(Level.INFO, "Accepted connection #{0} from {1}", new Object[]{id,s.getRemoteSocketAddress()});

DataInputStream in = new DataInputStream(s.getInputStream());
PrintWriter out = new PrintWriter(
Expand Down Expand Up @@ -183,6 +200,78 @@ private void error(PrintWriter out, String msg) throws IOException {
}
}

/**
* This extension provides a Ping protocol that allows people to verify that the TcpSlaveAgentListener is alive.
* We also use this
* @since
*/
@Extension
public static class PingAgentProtocol extends AgentProtocol {

private final byte[] ping;

public PingAgentProtocol() {
try {
ping = "Ping\n".getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("JLS mandates support for UTF-8 charset", e);
}
}

@Override
public String getName() {
return "Ping";
}

@Override
public void handle(Socket socket) throws IOException, InterruptedException {
try {
OutputStream stream = socket.getOutputStream();
try {
LOGGER.log(Level.FINE, "Received ping request from {0}", socket.getRemoteSocketAddress());
stream.write(ping);
stream.flush();
LOGGER.log(Level.FINE, "Sent ping response to {0}", socket.getRemoteSocketAddress());
} finally {
stream.close();
}
} finally {
socket.close();
}
}

public boolean connect(Socket socket) throws IOException {
try {
DataOutputStream out = null;
InputStream in = null;
try {
LOGGER.log(Level.FINE, "Requesting ping from {0}", socket.getRemoteSocketAddress());
out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("Protocol:Ping");
in = socket.getInputStream();
byte[] response = new byte[ping.length];
int responseLength = in.read(response);
if (responseLength == ping.length && Arrays.equals(response, ping)) {
LOGGER.log(Level.FINE, "Received ping response from {0}", socket.getRemoteSocketAddress());
return true;
} else {
LOGGER.log(Level.FINE, "Expected ping response from {0} of {1} got {2}", new Object[]{
socket.getRemoteSocketAddress(),
new String(ping, "UTF-8"),
new String(response, 0, responseLength, "UTF-8")
});
return false;
}
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
} finally {
socket.close();
}
}
}

/**
* Connection terminated because we are reconnected from the current peer.
*/
Expand Down Expand Up @@ -238,4 +327,4 @@ Note also that the JAR file(s) for the JWS application should not be on
can't do anything useful without going through a protected path or doing
something to present credentials that could only have come from a valid
user.
*/
*/

0 comments on commit 2a61f30

Please sign in to comment.