in core/src/main/java/hudson/slaves/SlaveComputer.java [608:728]
public void setChannel(@NonNull Channel channel,
@CheckForNull OutputStream launchLog,
@CheckForNull Channel.Listener listener) throws IOException, InterruptedException {
if(this.channel!=null)
throw new IllegalStateException("Already connected");
final TaskListener taskListener = launchLog != null ? new StreamTaskListener(launchLog) : TaskListener.NULL;
PrintStream log = taskListener.getLogger();
channel.setProperty(SlaveComputer.class, this);
channel.addListener(new LoggingChannelListener(logger, Level.FINEST) {
@Override
public void onClosed(Channel c, IOException cause) {
// Orderly shutdown will have null exception
if (cause!=null) {
offlineCause = new ChannelTermination(cause);
Functions.printStackTrace(cause, taskListener.error("Connection terminated"));
} else {
taskListener.getLogger().println("Connection terminated");
}
closeChannel();
try {
launcher.afterDisconnect(SlaveComputer.this, taskListener);
} catch (Throwable t) {
LogRecord lr = new LogRecord(Level.SEVERE,
"Launcher {0}'s afterDisconnect method propagated an exception when {1}'s connection was closed: {2}");
lr.setThrown(t);
lr.setParameters(new Object[]{launcher, SlaveComputer.this.getName(), t.getMessage()});
logger.log(lr);
}
}
});
if(listener!=null)
channel.addListener(listener);
String slaveVersion = channel.call(new SlaveVersion());
log.println("Remoting version: " + slaveVersion);
VersionNumber agentVersion = new VersionNumber(slaveVersion);
if (agentVersion.isOlderThan(RemotingVersionInfo.getMinimumSupportedVersion())) {
log.println(String.format("WARNING: Remoting version is older than a minimum required one (%s). " +
"Connection will not be rejected, but the compatibility is NOT guaranteed",
RemotingVersionInfo.getMinimumSupportedVersion()));
}
boolean _isUnix = channel.call(new DetectOS());
log.println(_isUnix? hudson.model.Messages.Slave_UnixSlave():hudson.model.Messages.Slave_WindowsSlave());
String defaultCharsetName = channel.call(new DetectDefaultCharset());
Slave node = getNode();
if (node == null) { // Node has been disabled/removed during the connection
throw new IOException("Node "+nodeName+" has been deleted during the channel setup");
}
String remoteFS = node.getRemoteFS();
if (Util.isRelativePath(remoteFS)) {
remoteFS = channel.call(new AbsolutePath(remoteFS));
log.println("NOTE: Relative remote path resolved to: "+remoteFS);
}
if(_isUnix && !remoteFS.contains("/") && remoteFS.contains("\\"))
log.println("WARNING: "+remoteFS
+" looks suspiciously like Windows path. Maybe you meant "+remoteFS.replace('\\','/')+"?");
FilePath root = new FilePath(channel,remoteFS);
// reference counting problem is known to happen, such as JENKINS-9017, and so as a preventive measure
// we pin the base classloader so that it'll never get GCed. When this classloader gets released,
// it'll have a catastrophic impact on the communication.
channel.pinClassLoader(getClass().getClassLoader());
channel.call(new SlaveInitializer(DEFAULT_RING_BUFFER_SIZE));
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
for (ComputerListener cl : ComputerListener.all()) {
cl.preOnline(this,channel,root,taskListener);
}
}
offlineCause = null;
// update the data structure atomically to prevent others from seeing a channel that's not properly initialized yet
synchronized(channelLock) {
if(this.channel!=null) {
// check again. we used to have this entire method in a big synchronization block,
// but Channel constructor blocks for an external process to do the connection
// if CommandLauncher is used, and that cannot be interrupted because it blocks at InputStream.
// so if the process hangs, it hangs the thread in a lock, and since Hudson will try to relaunch,
// we'll end up queuing the lot of threads in a pseudo deadlock.
// This implementation prevents that by avoiding a lock. HUDSON-1705 is likely a manifestation of this.
channel.close();
throw new IllegalStateException("Already connected");
}
isUnix = _isUnix;
numRetryAttempt = 0;
this.channel = channel;
this.absoluteRemoteFs = remoteFS;
defaultCharset = Charset.forName(defaultCharsetName);
synchronized (statusChangeLock) {
statusChangeLock.notifyAll();
}
}
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
for (ComputerListener cl : ComputerListener.all()) {
try {
cl.onOnline(this,taskListener);
} catch (Exception e) {
// Per Javadoc log exceptions but still go online.
// NOTE: this does not include Errors, which indicate a fatal problem
taskListener.getLogger().format(
"onOnline: %s reported an exception: %s%n",
cl.getClass(),
e.toString());
} catch (Throwable e) {
closeChannel();
throw e;
}
}
}
log.println("Agent successfully connected and online");
Jenkins.get().getQueue().scheduleMaintenance();
}