Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 312383

Summary: jgit hangs when try to fetch through ssh
Product: [Technology] JGit Reporter: Dmitry Neverov <dmitry.neverov>
Component: JGitAssignee: Project Inbox <jgit.core-inbox>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: dmitry.neverov, sop
Version: unspecified   
Target Milestone: 0.8.0   
Hardware: PC   
OS: Linux   
Whiteboard:
Attachments:
Description Flags
patch to fix problem
none
test to reproduce problem none

Description Dmitry Neverov CLA 2010-05-11 04:50:36 EDT
Build Identifier: standalone jgit library

jgit hangs and returns only when timeout is expired when try to fetch through ssh.

Reproducible: Always

Steps to Reproduce:
1. Here is the test to reproduce this problem:

  public void testSshFetch() throws IOException, URISyntaxException {
    String url = "ssh://test@localhost:22/home/test/test-project";

    File localRepositoryDir = new File("/tmp/test-ssh");
    deleteFile(localRepositoryDir);

    Repository r = new Repository(localRepositoryDir);
    r.create(true);
    final RepositoryConfig config = r.getConfig();
    config.setString("master", null, "remote", url);
    config.save();

    final Transport t = Transport.open(r, url);
    SshTransport ssh = (SshTransport)t;
    ssh.setSshSessionFactory(new SshSessionFactory() {
      private final JSch sch = new JSch();
      @Override
      public Session getSession(String user, String pass, String host, int port) throws JSchException {
        final Session session = sch.getSession(user, host, port);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPassword("test");
        return session;
      }
    });
    t.setTimeout(100);

    try {
      final String refName = "refs/heads/master";
      RefSpec spec = new RefSpec().setSource(refName).setDestination(refName).setForceUpdate(true);
      FetchResult fr = t.fetch(NullProgressMonitor.INSTANCE, Collections.singletonList(spec));
      assertNotNull(fr);
    } finally {
      t.close();
    }
  }

  Sometimes it runs without error, but more othen it hangs and return with exception:
  
  org.eclipse.jgit.errors.TransportException: Read timed out
	at org.eclipse.jgit.transport.BasePackConnection.readAdvertisedRefs(BasePackConnection.java:180)
	at org.eclipse.jgit.transport.TransportGitSsh$SshFetchConnection.<init>(TransportGitSsh.java:265)
	at org.eclipse.jgit.transport.TransportGitSsh.openFetch(TransportGitSsh.java:98)
	at org.eclipse.jgit.transport.FetchProcess.executeImp(FetchProcess.java:119)
	at org.eclipse.jgit.transport.FetchProcess.execute(FetchProcess.java:109)
	at org.eclipse.jgit.transport.Transport.fetch(Transport.java:814)
	at org.eclipse.jgit.transport.SshCloneTest.testSshFetch(SshCloneTest.java:50)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at com.intellij.junit3.JUnit3IdeaTestRunner.doRun(JUnit3IdeaTestRunner.java:108)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:64)


2. It seems to me that reason for that is that you connect to exec
channel of ssh session before set it's input and output streams. When
channel connects it writes something to remote host and remote host
answers something, but since output stream is not set we do not get
this answer and wait for it until timeout is over.

3. To fix this problem you can change method exec() in TransportGitSsh.java:

	ChannelExec exec(final String exe) throws TransportException {
		initSession();

		final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
		try {
			final ChannelExec channel = (ChannelExec) sock.openChannel("exec");
			channel.setCommand(commandFor(exe));
      //channel.connect(tms); // <- do not connect until input, output and extOutput streams are set
      return channel;
		} catch (JSchException je) {
			throw new TransportException(uri, je.getMessage(), je);
		}
  }

and constructor of class TransportGitSsh.SshFetchConnection:

		SshFetchConnection() throws TransportException {
			super(TransportGitSsh.this);
			try {
				final MessageWriter msg = new MessageWriter();
				setMessageWriter(msg);

				channel = exec(getOptionUploadPack());
        //if (!channel.isConnected())
        //  throw new TransportException(uri, "connection failed"); // <- do not connect until all streams are set

				final InputStream upErr = channel.getErrStream();
				errorThread = new StreamCopyThread(upErr, msg.getRawStream());
				errorThread.start();

				init(channel.getInputStream(), outputStream(channel));

			} catch (TransportException err) {
				close();
				throw err;
			} catch (IOException err) {
				close();
				throw new TransportException(uri,
						"remote hung up unexpectedly", err);
			}

			try {
				channel.connect(getTimeout()); // <- now we can connect
				if (!channel.isConnected())
					throw new TransportException(uri, "connection failed");
			} catch (JSchException e) {
				throw new TransportException(uri, e.getMessage(), e);
			}

      try {
				readAdvertisedRefs();
			} catch (NoRemoteRepositoryException notFound) {
				final String msgs = getMessages();
				checkExecFailure(exitStatus, getOptionUploadPack(), msgs);
				throw cleanNotFound(notFound, msgs);
			}
		}

I hope it will help.
Comment 1 Dmitry Neverov CLA 2010-05-11 04:54:41 EDT
Created attachment 167876 [details]
patch to fix problem
Comment 2 Dmitry Neverov CLA 2010-05-11 04:56:32 EDT
Created attachment 167878 [details]
test to reproduce problem
Comment 3 Shawn Pearce CLA 2010-05-13 13:26:42 EDT
Fixed by 3cba5377dfdad8c36d1f3b0642f56549821b8676