Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
import java.util.logging.Logger;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.remoting.ChannelClosedException;
import java.io.EOFException;
import java.nio.channels.ClosedChannelException;
import java.util.stream.Stream;
import jenkins.MasterToSlaveFileCallable;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
Expand Down Expand Up @@ -541,8 +545,8 @@ public FilePath getOutputFile(FilePath workspace) throws IOException, Interrupte
}

@Override public void watch(FilePath workspace, Handler handler, TaskListener listener) throws IOException, InterruptedException, ClassCastException {
workspace.act(new StartWatching(this, handler, listener));
LOGGER.log(Level.FINE, "started asynchronous watch in {0}", controlDir);
workspace.actAsync(new StartWatching(this, handler, listener));
LOGGER.log(Level.FINE, "started asynchronous watch in " + controlDir, new Throwable());
}

/**
Expand Down Expand Up @@ -576,6 +580,21 @@ private static class StartWatching extends MasterToSlaveFileCallable<Void> {

}

// TODO https://github.com/jenkinsci/remoting/pull/657
private static boolean isClosedChannelException(Throwable t) {
if (t instanceof ClosedChannelException) {
return true;
} else if (t instanceof ChannelClosedException) {
return true;
} else if (t instanceof EOFException) {
return true;
} else if (t == null) {
return false;
} else {
return isClosedChannelException(t.getCause()) || Stream.of(t.getSuppressed()).anyMatch(FileMonitoringTask::isClosedChannelException);
}
}

private static class Watcher implements Runnable {

private final FileMonitoringController controller;
Expand All @@ -585,6 +604,7 @@ private static class Watcher implements Runnable {
private final @CheckForNull Charset cs;

Watcher(FileMonitoringController controller, FilePath workspace, Handler handler, TaskListener listener) {
LOGGER.log(Level.FINE, "starting " + this, new Throwable());
this.controller = controller;
this.workspace = workspace;
this.handler = handler;
Expand All @@ -611,7 +631,8 @@ private static class Watcher implements Runnable {
handler.output(utf8EncodedStream);
long newLocation = ch.position();
lastLocationFile.write(Long.toString(newLocation), null);
LOGGER.log(Level.FINER, "copied {0} bytes from {1}", new Object[] {newLocation - lastLocation, logFile});
long delta = newLocation - lastLocation;
LOGGER.finer(() -> this + " copied " + delta + " bytes from " + logFile);
}
}
if (exitStatus != null) {
Expand All @@ -621,7 +642,7 @@ private static class Watcher implements Runnable {
} else {
output = null;
}
LOGGER.log(Level.FINE, "exiting with code {0}", exitStatus);
LOGGER.fine(() -> this + " exiting with code " + exitStatus);
handler.exited(exitStatus, output);
controller.cleanup(workspace);
} else {
Expand All @@ -636,7 +657,11 @@ private static class Watcher implements Runnable {
}
} catch (Exception x) {
// note that LOGGER here is going to the agent log, not master log
LOGGER.log(Level.WARNING, "giving up on watching " + controller.controlDir, x);
if (isClosedChannelException(x)) {
LOGGER.warning(() -> this + " giving up on watching " + controller.controlDir);
} else {
LOGGER.log(Level.WARNING, this + " giving up on watching " + controller.controlDir, x);
}
// Typically this will have been inside Handler.output, e.g.:
// hudson.remoting.ChannelClosedException: channel is already closed
// at hudson.remoting.Channel.send(Channel.java:667)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
import java.io.Serializable;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.remoting.Asynchronous;
import org.jenkinsci.remoting.SerializableOnlyOverRemoting;

/**
* A remote handler which may be sent to an agent and handle process output and results.
* If it needs to communicate with the master, you may use {@link VirtualChannel#export}.
* @see Controller#watch
*/
public abstract class Handler implements Serializable { // TODO 2.107+ SerializableOnlyOverRemoting
public abstract class Handler implements SerializableOnlyOverRemoting {

/**
* Notification that new process output is available.
Expand All @@ -60,8 +62,8 @@ public abstract class Handler implements Serializable { // TODO 2.107+ Serializa
* you still need to occasionally poll for an exit status from the master.
* @param code the exit code, if known (0 conventionally represents success); may be negative for anomalous conditions such as a missing process
* @param output standard output captured, if {@link DurableTask#captureOutput} was called; else null
* @throws Exception if anything goes wrong, this watch is deactivated
*/
@Asynchronous
public abstract void exited(int code, @Nullable byte[] output) throws Exception;

}