Update javadoc for RemoteSession and SshSessionFactory

The timeout on RemoteSession.exec() cannot be a timeout for the
whole command. It can only be a timeout for setting up the process;
after that it's the application's responsibility to implement some
timeout for the execution of the command, for instance by calling
Process.waitFor(int, TimeUnit) or through other means.

Sessions returned by an SshSessionFactory are already connected and
authenticated -- they must be, because RemoteSession offers no
operations for connecting or authenticating a session.

Change the implementation of SshdExecProcess.waitFor() to wait
indefinitely. The original implementation used the timeout from
RemoteSession.exec() because of that erroneous javadoc.

Change-Id: I3c7ede24ab66d4c81f72d178ce5012d383cd826e
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2020-08-08 11:53:43 +02:00
parent 24fdc1d039
commit 72b111ecd7
3 changed files with 52 additions and 67 deletions

View File

@ -13,7 +13,6 @@
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -134,28 +133,23 @@ private void notifyCloseListeners() {
public Process exec(String commandName, int timeout) throws IOException { public Process exec(String commandName, int timeout) throws IOException {
@SuppressWarnings("resource") @SuppressWarnings("resource")
ChannelExec exec = session.createExecChannel(commandName); ChannelExec exec = session.createExecChannel(commandName);
long timeoutMillis = TimeUnit.SECONDS.toMillis(timeout); if (timeout <= 0) {
try { try {
if (timeout <= 0) {
exec.open().verify(); exec.open().verify();
} else { } catch (IOException | RuntimeException e) {
long start = System.nanoTime(); exec.close(true);
exec.open().verify(timeoutMillis); throw e;
timeoutMillis -= TimeUnit.NANOSECONDS }
.toMillis(System.nanoTime() - start); } else {
try {
exec.open().verify(TimeUnit.SECONDS.toMillis(timeout));
} catch (IOException | RuntimeException e) {
exec.close(true);
throw new IOException(format(SshdText.get().sshCommandTimeout,
commandName, Integer.valueOf(timeout)), e);
} }
} catch (IOException | RuntimeException e) {
exec.close(true);
throw e;
} }
if (timeout > 0 && timeoutMillis <= 0) { return new SshdExecProcess(exec, commandName);
// We have used up the whole timeout for opening the channel
exec.close(true);
throw new InterruptedIOException(
format(SshdText.get().sshCommandTimeout, commandName,
Integer.valueOf(timeout)));
}
return new SshdExecProcess(exec, commandName, timeoutMillis);
} }
/** /**
@ -195,14 +189,10 @@ private static class SshdExecProcess extends Process {
private final ChannelExec channel; private final ChannelExec channel;
private final long timeoutMillis;
private final String commandName; private final String commandName;
public SshdExecProcess(ChannelExec channel, String commandName, public SshdExecProcess(ChannelExec channel, String commandName) {
long timeoutMillis) {
this.channel = channel; this.channel = channel;
this.timeoutMillis = timeoutMillis > 0 ? timeoutMillis : -1L;
this.commandName = commandName; this.commandName = commandName;
} }
@ -223,7 +213,7 @@ public InputStream getErrorStream() {
@Override @Override
public int waitFor() throws InterruptedException { public int waitFor() throws InterruptedException {
if (waitFor(timeoutMillis, TimeUnit.MILLISECONDS)) { if (waitFor(-1L, TimeUnit.MILLISECONDS)) {
return exitValue(); return exitValue();
} }
return -1; return -1;

View File

@ -4,7 +4,7 @@
* Copyright (C) 2009, Google, Inc. * Copyright (C) 2009, Google, Inc.
* Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2009, JetBrains s.r.o.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -18,26 +18,20 @@
import java.io.IOException; import java.io.IOException;
/** /**
* Create a remote "session" for executing remote commands. * An abstraction of a remote "session" for executing remote commands.
* <p>
* Clients should subclass RemoteSession to create an alternate way for JGit to
* execute remote commands. (The client application may already have this
* functionality available.) Note that this class is just a factory for creating
* remote processes. If the application already has a persistent connection to
* the remote machine, RemoteSession may do nothing more than return a new
* RemoteProcess when exec is called.
*/ */
public interface RemoteSession { public interface RemoteSession {
/** /**
* Generate a new remote process to execute the given command. This function * Creates a new remote {@link Process} to execute the given command. The
* should also start execution and may need to create the streams prior to * returned process's streams exist and are connected, and execution of the
* execution. * process is already started.
* *
* @param commandName * @param commandName
* command to execute * command to execute
* @param timeout * @param timeout
* timeout value, in seconds, for command execution * timeout value, in seconds, for creating the remote process
* @return a new remote process * @return a new remote process, already started
* @throws java.io.IOException * @throws java.io.IOException
* may be thrown in several cases. For example, on problems * may be thrown in several cases. For example, on problems
* opening input or output streams or on problems connecting or * opening input or output streams or on problems connecting or
@ -48,7 +42,7 @@ public interface RemoteSession {
Process exec(String commandName, int timeout) throws IOException; Process exec(String commandName, int timeout) throws IOException;
/** /**
* Obtain an {@link FtpChannel} for performing FTP operations over this * Obtains an {@link FtpChannel} for performing FTP operations over this
* {@link RemoteSession}. The default implementation returns {@code null}. * {@link RemoteSession}. The default implementation returns {@code null}.
* *
* @return the {@link FtpChannel} * @return the {@link FtpChannel}
@ -59,7 +53,7 @@ default FtpChannel getFtpChannel() {
} }
/** /**
* Disconnect the remote session * Disconnects the remote session.
*/ */
void disconnect(); void disconnect();
} }

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -27,12 +27,15 @@
* Different implementations of the session factory may be used to control * Different implementations of the session factory may be used to control
* communicating with the end-user as well as reading their personal SSH * communicating with the end-user as well as reading their personal SSH
* configuration settings, such as known hosts and private keys. * configuration settings, such as known hosts and private keys.
* </p>
* <p> * <p>
* A {@link org.eclipse.jgit.transport.RemoteSession} must be returned to the * A {@link RemoteSession} must be returned to the factory that created it.
* factory that created it. Callers are encouraged to retain the * Callers are encouraged to retain the SshSessionFactory for the duration of
* SshSessionFactory for the duration of the period they are using the Session. * the period they are using the session.
* </p>
*/ */
public abstract class SshSessionFactory { public abstract class SshSessionFactory {
private static SshSessionFactory INSTANCE = loadSshSessionFactory(); private static SshSessionFactory INSTANCE = loadSshSessionFactory();
private static SshSessionFactory loadSshSessionFactory() { private static SshSessionFactory loadSshSessionFactory() {
@ -45,10 +48,11 @@ private static SshSessionFactory loadSshSessionFactory() {
} }
/** /**
* Get the currently configured JVM-wide factory. * Gets the currently configured JVM-wide factory.
* <p> * <p>
* By default the factory will read from the user's <code>$HOME/.ssh</code> * By default the factory will read from the user's {@code $HOME/.ssh} and
* and assume OpenSSH compatibility. * assume OpenSSH compatibility.
* </p>
* *
* @return factory the current factory for this JVM. * @return factory the current factory for this JVM.
*/ */
@ -57,11 +61,11 @@ public static SshSessionFactory getInstance() {
} }
/** /**
* Change the JVM-wide factory to a different implementation. * Changes the JVM-wide factory to a different implementation.
* *
* @param newFactory * @param newFactory
* factory for future sessions to be created through. If null the * factory for future sessions to be created through; if
* default factory will be restored. * {@code null} the default factory will be restored.
*/ */
public static void setInstance(SshSessionFactory newFactory) { public static void setInstance(SshSessionFactory newFactory) {
if (newFactory != null) { if (newFactory != null) {
@ -85,26 +89,23 @@ public static String getLocalUserName() {
} }
/** /**
* Open (or reuse) a session to a host. * Opens (or reuses) a session to a host. The returned session is connected
* <p> * and authenticated and is ready for further use.
* A reasonable UserInfo that can interact with the end-user (if necessary)
* is installed on the returned session by this method.
* <p>
* The caller must connect the session by invoking <code>connect()</code> if
* it has not already been connected.
* *
* @param uri * @param uri
* URI information about the remote host * URI of the remote host to connect to
* @param credentialsProvider * @param credentialsProvider
* provider to support authentication, may be null. * provider to support authentication, may be {@code null} if no
* user input for authentication is needed
* @param fs * @param fs
* the file system abstraction which will be necessary to perform * the file system abstraction to use for certain file
* certain file system operations. * operations, such as reading configuration files
* @param tms * @param tms
* Timeout value, in milliseconds. * connection timeout for creating the session, in milliseconds
* @return a session that can contact the remote host. * @return a connected and authenticated session for communicating with the
* remote host given by the {@code uri}
* @throws org.eclipse.jgit.errors.TransportException * @throws org.eclipse.jgit.errors.TransportException
* the session could not be created. * if the session could not be created
*/ */
public abstract RemoteSession getSession(URIish uri, public abstract RemoteSession getSession(URIish uri,
CredentialsProvider credentialsProvider, FS fs, int tms) CredentialsProvider credentialsProvider, FS fs, int tms)
@ -120,7 +121,7 @@ public abstract RemoteSession getSession(URIish uri,
public abstract String getType(); public abstract String getType();
/** /**
* Close (or recycle) a session to a host. * Closes (or recycles) a session to a host.
* *
* @param session * @param session
* a session previously obtained from this factory's * a session previously obtained from this factory's