title: Timeout when ssh command is executed from my Java program
body: <p>I have a Java program which runs in Tomcat and which needs to execute several ssh and scp commands, as well as a few simple commands such as ls on the local machine.  I am having trouble with my current approach in that I am getting a time out every time I execute an ssh command.  I can run the ssh command on the command line with no problems, but when it's executed from my Java program it times out.  I am running the web application in which the ssh commands are executed as root (i.e. I start Tomcat as root user, with my web application code deployed as a WAR file), and as far as I know the correct certification keys are configured on both the local and remote machines, at least I can perform the ssh commands at command line as root without having to enter a user name or password.  I do not specify the user name or password in the ssh command that is being executed by my Java program since I assume that I can run the same ssh command in my Java code as I can execute at command line, but maybe this is a false assumption and is the cause of my trouble.</p>
  
  <p>The Java code I have developed to perform command execution is as follows:</p>
  
  <pre><code>public class ProcessUtility
  {
  
      static Log log = LogFactory.getLog(ProcessUtility.class);
  
      /**
       * Thread class to be used as a worker
       */
      private static class Worker
          extends Thread
      {
          private final Process process;
          private volatile Integer exitValue;
  
          Worker(final Process process)
          {
              this.process = process;
          }
  
          public Integer getExitValue()
          {
              return exitValue;
          }
  
          @Override
          public void run()
          {
              try
              {
                  exitValue = process.waitFor();
              }
              catch (InterruptedException ignore)
              {
                  return;
              }
          }
      }
  
      /**
       * Executes a command.
       * 
       * @param args command + arguments
       */
      public static void execCommand(final String[] args)
      {
          try
          {
              Runtime.getRuntime().exec(args);
          }
          catch (IOException e)
          {
              // swallow it
          }
  
      }
  
      /**
       * Executes a command.
       * 
       * @param command
       * @param printOutput
       * @param printError
       * @param timeOut
       * @return
       * @throws java.io.IOException
       * @throws java.lang.InterruptedException
       */
      public static int executeCommand(final String command,
                                       final boolean printOutput,
                                       final boolean printError,
                                       final long timeOut)
      {
          return executeCommandWithWorker(command, printOutput, printError, timeOut);
      }
  
      /**
       * Executes a command and returns its output or error stream.
       * 
       * @param command
       * @return the command's resulting output or error stream
       */
      public static String executeCommandReceiveOutput(final String command)
      {
          try
          {
              // create the process which will run the command
              Runtime runtime = Runtime.getRuntime();
              final Process process = runtime.exec(command);
  
              try
              {
                  // consume the error and output streams
                  StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", false);
                  StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", false);
                  outputGobbler.start();
                  errorGobbler.start();
  
                  // execute the command
                  if (process.waitFor() == 0)
                  {
                      return outputGobbler.getInput();
                  }
                  return errorGobbler.getInput();
              }
              finally
              {
                  process.destroy();
              }
          }
          catch (InterruptedException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
          catch (IOException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
      }
  
      /**
       * Executes a command.
       * 
       * @param command
       * @param printOutput
       * @param printError
       * @param timeOut
       * @return
       * @throws java.io.IOException
       * @throws java.lang.InterruptedException
       */
      @SuppressWarnings("unused")
      private static int executeCommandWithExecutors(final String command,
                                                     final boolean printOutput,
                                                     final boolean printError,
                                                     final long timeOut)
      {
          // validate the system and command line and get a system-appropriate command line 
          String massagedCommand = validateSystemAndMassageCommand(command);
  
          try
          {
              // create the process which will run the command
              Runtime runtime = Runtime.getRuntime();
              final Process process = runtime.exec(massagedCommand);
  
              // consume and display the error and output streams
              StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
              StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
              outputGobbler.start();
              errorGobbler.start();
  
              // create a Callable for the command's Process which can be called by an Executor 
              Callable&lt;Integer&gt; call = new Callable&lt;Integer&gt;()
              {
                  public Integer call()
                      throws Exception
                  {
                      process.waitFor();
                      return process.exitValue();
                  }
              };
  
              // submit the command's call via an Executor and get the result from a Future
              ExecutorService executorService = Executors.newSingleThreadExecutor();
              try
              {
                  Future&lt;Integer&gt; futureResultOfCall = executorService.submit(call);
                  int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS);
                  return exitValue;
              }
              catch (TimeoutException ex)
              {
                  String errorMessage = "The command [" + command + "] timed out.";
                  log.error(errorMessage, ex);
                  throw new RuntimeException(errorMessage, ex);
              }
              catch (ExecutionException ex)
              {
                  String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
                  log.error(errorMessage, ex);
                  throw new RuntimeException(errorMessage, ex);
              }
              finally
              {
                  executorService.shutdown();
                  process.destroy();
              }
          }
          catch (InterruptedException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
          catch (IOException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
      }
  
      /**
       * Executes a command.
       * 
       * @param command
       * @param printOutput
       * @param printError
       * @param timeOut
       * @return
       * @throws java.io.IOException
       * @throws java.lang.InterruptedException
       */
      private static int executeCommandWithWorker(final String command,
                                                  final boolean printOutput,
                                                  final boolean printError,
                                                  final long timeOut)
      {
          // validate the system and command line and get a system-appropriate command line 
          String massagedCommand = validateSystemAndMassageCommand(command);
  
          try
          {
              // create the process which will run the command
              Runtime runtime = Runtime.getRuntime();
              Process process = runtime.exec(massagedCommand);
  
              // consume and display the error and output streams
              StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
              StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
              outputGobbler.start();
              errorGobbler.start();
  
              // create and start a Worker thread which this thread will join for the timeout period 
              Worker worker = new Worker(process);
              worker.start();
              try
              {
                  worker.join(timeOut);
                  Integer exitValue = worker.getExitValue();
                  if (exitValue != null)
                  {
                      // the worker thread completed within the timeout period
  
                      // stop the output and error stream gobblers
                      outputGobbler.stopGobbling();
                      errorGobbler.stopGobbling();
  
                      return exitValue;
                  }
  
                  // if we get this far then we never got an exit value from the worker thread as a result of a timeout 
                  String errorMessage = "The command [" + command + "] timed out.";
                  log.error(errorMessage);
                  throw new RuntimeException(errorMessage);
              }
              catch (InterruptedException ex)
              {
                  worker.interrupt();
                  Thread.currentThread().interrupt();
                  throw ex;
              }
              finally
              {
                  process.destroy();
              }
          }
          catch (InterruptedException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
          catch (IOException ex)
          {
              String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
              log.error(errorMessage, ex);
              throw new RuntimeException(errorMessage, ex);
          }
      }
  
      /**
       * Validates that the system is running a supported OS and returns a system-appropriate command line.
       * 
       * @param originalCommand
       * @return
       */
      private static String validateSystemAndMassageCommand(final String originalCommand)
      {
          // make sure that we have a command
          if (originalCommand.isEmpty() || (originalCommand.length() &lt; 1))
          {
              String errorMessage = "Missing or empty command line parameter.";
              log.error(errorMessage);
              throw new RuntimeException(errorMessage);
          }
  
          // make sure that we are running on a supported system, and if so set the command line appropriately
          String massagedCommand;
          String osName = System.getProperty("os.name");
          if (osName.equals("Windows XP"))
          {
              massagedCommand = "cmd.exe /C " + originalCommand;
          }
          else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux"))
          {
              massagedCommand = originalCommand;
          }
          else
          {
              String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" +
                                    osName + "\').";
              log.error(errorMessage);
              throw new RuntimeException(errorMessage);
          }
  
          return massagedCommand;
      }
  }
  
  class StreamGobbler
      extends Thread
  {
      static private Log log = LogFactory.getLog(StreamGobbler.class);
      private InputStream inputStream;
      private String streamType;
      private boolean displayStreamOutput;
      private final StringBuffer inputBuffer = new StringBuffer();
      private boolean keepGobbling = true;
  
      /**
       * Constructor.
       * 
       * @param inputStream the InputStream to be consumed
       * @param streamType the stream type (should be OUTPUT or ERROR)
       * @param displayStreamOutput whether or not to display the output of the stream being consumed
       */
      StreamGobbler(final InputStream inputStream,
                    final String streamType,
                    final boolean displayStreamOutput)
      {
          this.inputStream = inputStream;
          this.streamType = streamType;
          this.displayStreamOutput = displayStreamOutput;
      }
  
      /**
       * Returns the output stream of the
       * 
       * @return
       */
      public String getInput()
      {
          return inputBuffer.toString();
      }
  
      /**
       * Consumes the output from the input stream and displays the lines consumed if configured to do so.
       */
      @Override
      public void run()
      {
          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
          try
          {
              String line = null;
              while (keepGobbling &amp;&amp; inputStreamReader.ready() &amp;&amp; ((line = bufferedReader.readLine()) != null))
              {
                  inputBuffer.append(line);
                  if (displayStreamOutput)
                  {
                      System.out.println(streamType + "&gt;" + line);
                  }
              }
          }
          catch (IOException ex)
          {
              log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
              ex.printStackTrace();
          }
          finally
          {
              try
              {
                  bufferedReader.close();
                  inputStreamReader.close();
              }
              catch (IOException e)
              {
                  // swallow it
              }
          }
      }
  
      public void stopGobbling()
      {
          keepGobbling = false;
      }
  }
  </code></pre>
  
  <p>I execute the ssh commands in my Java program like so:</p>
  
  <p>ProcessUtility.executeCommand("ssh " + physicalHostIpAddress + " virsh list \| grep " +
                                            newDomUName, false, false, 3600000)</p>
  
  <p>Can anyone see what I am doing wrong?  BTW the above code was developed using this article as a guide: <a href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html" rel="nofollow noreferrer">http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html</a>.  I am not very expert with concurrent programming so perhaps I am doing something boneheaded -- feel free to point it out if so.</p>
  
  <p>Thanks a lot in advance for any suggestions, ideas, etc.</p>
  
  <p>--James</p>
  
  <p><strong>Update</strong>: I have now taken the advice of the helpful folks who responded to my original question and have written a class which provides methods for making ssh and scp calls, implemented using two Java ssh libraries jsch (jsch-0.1.31) and sshtools (j2ssh-core-0.2.9).  However neither of these implementations are working in that they are both failing at the connect step, before I even get the chance to perform authentication.  I expect that I am facing some sort of configuration issue on the servers where I am running the codes, although this is not obvious since I can perform ssh and scp commands on these servers with no problems when I issue the ssh or scp commands on the command line.  The Solaris servers that I am testing my code on are showing the following as a result of ssh -V:</p>
  
  <p>Sun_SSH_1.3, SSH protocols 1.5/2.0, OpenSSL 0x0090801f</p>
  
  <p>Below is the Java code I have written for this purpose -- if anyone can see what I am doing wrong at the Java code level then please let me know, and if so thanks a lot in advance for your help.</p>
  
  <pre><code>public class SecureCommandUtility
  {
      static Log log = LogFactory.getLog(SecureCommandUtility.class);
  
      /**
       * Performs a secure copy of a single file (using scp).
       * 
       * @param localFilePathName
       * @param username
       * @param password
       * @param remoteHost
       * @param remoteFilePathName
       * @param timeout
       */
      public static void secureCopySingleFile(final String localFilePathName,
                                              final String username,
                                              final String password,
                                              final String remoteHost,
                                              final String remoteFilePathName,
                                              final int timeout)
      {
          // basic validation of the parameters
          if ((localFilePathName == null) || localFilePathName.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied local file path name parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((username == null) || username.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied user name parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((password == null) || password.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied password parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((remoteHost == null) || remoteHost.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied remote host parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((remoteFilePathName == null) || remoteFilePathName.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied remote file path name parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if (timeout &lt; 1000)
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the secure copy -- the supplied timeout parameter is less than one second.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
  
          //secureCopySingleFileJSch(localFilePathName, username, password, remoteHost, remoteFilePathName);
          secureCopySingleFileJ2Ssh(localFilePathName, username, password, remoteHost, remoteFilePathName, timeout);
      }
  
      /**
       * 
       * @param user
       * @param password
       * @param remoteHost
       * @param command
       * @return exit status of the command
       */
      public static int secureShellCommand(final String user,
                                           final String password,
                                           final String remoteHost,
                                           final String command,
                                           final int timeout)
      {
          // basic validation of the parameters
          if ((user == null) || user.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the ssh command \'" + command +
                                    "\': the supplied user name parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((password == null) || password.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the ssh command \'" + command +
                                    "\': the supplied password parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((remoteHost == null) || remoteHost.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the ssh command \'" + command +
                                    "\': the supplied remote host parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if ((command == null) || command.isEmpty())
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the ssh command: the supplied command parameter is null or empty.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
          if (timeout &lt; 1000)
          {
              // log the error and throw an exception
              String errorMessage = "Error executing the ssh command \'" + command +
                                    "\': the supplied timeout parameter is less than one second.";
              log.error(errorMessage);
              throw new LifecycleException(errorMessage);
          }
  
          //return secureShellCommandJsch(user, password, remoteHost, command, timeout);
          return secureShellCommandJ2Ssh(user, password, remoteHost, command, timeout);
      }
  
      /**
       * Performs a secure copy of a single file (using scp).
       * 
       * @param localFilePathName
       * @param username
       * @param password
       * @param remoteHost
       * @param remoteFilePathName
       * @param timeout
       */
      private static void secureCopySingleFileJ2Ssh(final String localFilePathName,
                                                    final String username,
                                                    final String password,
                                                    final String remoteHost,
                                                    final String remoteFilePathName,
                                                    final int timeout)
      {
          SshClient sshClient = null;
          try
          {
              // create and connect client
              sshClient = new SshClient();
              sshClient.setSocketTimeout(timeout);
              sshClient.connect(remoteHost, 22, new IgnoreHostKeyVerification());
  
              // perform password-based authentication
              PasswordAuthenticationClient passwordAuthenticationClient = new PasswordAuthenticationClient();
              passwordAuthenticationClient.setUsername(username);
              passwordAuthenticationClient.setPassword(password);
              if (sshClient.authenticate(passwordAuthenticationClient) != AuthenticationProtocolState.COMPLETE)
              {
                  // log the error and throw an exception
                  String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
                                        remoteFilePathName + "\' -- failed to authenticate using username/password \'" +
                                        username + "\'/\'" + password + "\'.";
                  log.error(errorMessage);
                  throw new LifecycleException(errorMessage);
              }
  
              // perform the copy
              sshClient.openScpClient().put(localFilePathName, remoteFilePathName, false);
          }
          catch (Exception ex)
          {
              // log the error and throw an exception
              String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
                                    remoteFilePathName + "\'.";
              log.error(errorMessage, ex);
              throw new LifecycleException(errorMessage, ex);
          }
          finally
          {
              if ((sshClient != null) &amp;&amp; sshClient.isConnected())
              {
                  sshClient.disconnect();
              }
          }
      }
  
      /**
       * Performs a secure copy of a single file (using scp).
       * 
       * @param localFilePathName
       * @param user
       * @param password
       * @param remoteHost
       * @param remoteFilePathName
       */
      @SuppressWarnings("unused")
      private static void secureCopySingleFileJSch(final String localFilePathName,
                                                   final String user,
                                                   final String password,
                                                   final String remoteHost,
                                                   final String remoteFilePathName)
      {
          Session session = null;
          Channel channel = null;
          FileInputStream fileInputStream = null;
          try
          {
              // create and connect Jsch session
              JSch jsch = new JSch();
              session = jsch.getSession(user, remoteHost, 22);
              session.setPassword(password);
              session.connect();
  
              // exec 'scp -p -t remoteFilePathName' remotely
              String command = "scp -p -t " + remoteFilePathName;
              channel = session.openChannel("exec");
              ((ChannelExec) channel).setCommand(command);
  
              // get the I/O streams for the remote scp
              OutputStream outputStream = channel.getOutputStream();
              InputStream inputStream = channel.getInputStream();
  
              // connect the channel
              channel.connect();
  
              int ackCheck = checkAck(inputStream);
              if (checkAck(inputStream) != 0)
              {
                  // log the error and throw an exception
                  String errorMessage = "The scp command failed -- input stream ACK check failed with the following result: " +
                                        ackCheck;
                  log.error(errorMessage);
                  throw new LifecycleException(errorMessage);
              }
  
              // send "C0644 filesize filename", where filename should not include '/'
              long filesize = (new File(localFilePathName)).length();
              command = "C0644 " + filesize + " ";
              if (localFilePathName.lastIndexOf('/') &gt; 0)
              {
                  command += localFilePathName.substring(localFilePathName.lastInde
  </code></pre>