{"thread":{"id":"19868","subject":"[JGIT PATCH 0/6] Add timeouts to network IO","startedAt":"2009-06-19T21:27:49Z","lastAt":"2009-06-23T16:41:57Z","messageCount":11,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"116639","messageId":"1245446875-31102-1-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":null,"subject":"[JGIT PATCH 0/6] Add timeouts to network IO","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:49Z","receivedAt":"2009-06-19T21:27:49Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If there's a bug in the remote peer software, or even just random\nnetwork hardware/cable failures between us and the remote peer, we\nmight block indefinitely waiting for more incoming data, or waiting\nfor TCP ACKs necessary to release space in our transmit buffer.\n\nThis series adds a timeout to everything, allowing the caller to\ndefine some maximum waiting period before we abort and declare the\nremote peer to be unresponsive.  The default timeout of 0 will use\nthe traditional \"block indefinitely\" behavior we have always had.\n\n\nShawn O. Pearce (6):\n  Create input and output streams that have timeouts\n  Add remote.name.timeout to configure an IO timeout\n  Add timeouts to smart transport protocol clients\n  Add timeouts to smart transport protocol servers\n  Add timeouts to anonymous git:// daemon\n  Add --timeout command line options\n\n .../src/org/spearce/jgit/pgm/Daemon.java           |    5 +\n .../src/org/spearce/jgit/pgm/Fetch.java            |    5 +\n .../src/org/spearce/jgit/pgm/LsRemote.java         |    6 +\n .../src/org/spearce/jgit/pgm/Push.java             |    5 +\n .../src/org/spearce/jgit/pgm/UploadPack.java       |    6 +\n .../spearce/jgit/transport/RemoteConfigTest.java   |   26 ++\n .../jgit/util/io/TimeoutInputStreamTest.java       |  187 +++++++++++++\n .../jgit/util/io/TimeoutOutputStreamTest.java      |  286 ++++++++++++++++++++\n .../spearce/jgit/transport/BasePackConnection.java |   36 +++-\n .../jgit/transport/BasePackPushConnection.java     |   29 ++-\n .../src/org/spearce/jgit/transport/Daemon.java     |   26 ++-\n .../org/spearce/jgit/transport/DaemonClient.java   |   12 +-\n .../org/spearce/jgit/transport/ReceivePack.java    |   57 ++++\n .../org/spearce/jgit/transport/RemoteConfig.java   |   31 +++\n .../org/spearce/jgit/transport/SshTransport.java   |    3 +-\n .../src/org/spearce/jgit/transport/Transport.java  |   21 ++\n .../spearce/jgit/transport/TransportGitAnon.java   |   13 +-\n .../spearce/jgit/transport/TransportGitSsh.java    |  103 +++++++-\n .../org/spearce/jgit/transport/TransportLocal.java |    9 +-\n .../org/spearce/jgit/transport/TransportSftp.java  |    3 +-\n .../src/org/spearce/jgit/transport/UploadPack.java |   59 ++++-\n .../org/spearce/jgit/util/io/InterruptTimer.java   |  216 +++++++++++++++\n .../spearce/jgit/util/io/TimeoutInputStream.java   |  133 +++++++++\n .../spearce/jgit/util/io/TimeoutOutputStream.java  |  146 ++++++++++\n 24 files changed, 1400 insertions(+), 23 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n"},{"id":"116641","messageId":"1245446875-31102-2-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 1/6] Create input and output streams that have timeouts","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:50Z","receivedAt":"2009-06-19T21:27:50Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"We can use these streams to wrap around a PipedInputStream, such\nas the one given to us by JSch, to implement a timeout so we can\nabort a transport operation if the remote peer doesn't communicate\nwith us in a timely fashion.\n\nOnly the input and output streams and tests are defined, later\nI will make use of them in the transport layer where we can't\nrely upon raw socket timeouts to abort a long running IO call.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/util/io/TimeoutInputStreamTest.java       |  187 +++++++++++++\n .../jgit/util/io/TimeoutOutputStreamTest.java      |  286 ++++++++++++++++++++\n .../org/spearce/jgit/util/io/InterruptTimer.java   |  216 +++++++++++++++\n .../spearce/jgit/util/io/TimeoutInputStream.java   |  133 +++++++++\n .../spearce/jgit/util/io/TimeoutOutputStream.java  |  146 ++++++++++\n 5 files changed, 968 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\nnew file mode 100644\nindex 0000000..25eff9a\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n@@ -0,0 +1,187 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.PipedInputStream;\n+import java.io.PipedOutputStream;\n+import java.util.Arrays;\n+import java.util.List;\n+\n+import org.spearce.jgit.util.NB;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutInputStream;\n+\n+import junit.framework.TestCase;\n+\n+public class TimeoutInputStreamTest extends TestCase {\n+\tprivate static final int timeout = 250;\n+\n+\tprivate PipedOutputStream out;\n+\n+\tprivate PipedInputStream in;\n+\n+\tprivate InterruptTimer timer;\n+\n+\tprivate TimeoutInputStream is;\n+\n+\tprivate long start;\n+\n+\tprotected void setUp() throws Exception {\n+\t\tsuper.setUp();\n+\t\tout = new PipedOutputStream();\n+\t\tin = new PipedInputStream(out);\n+\t\ttimer = new InterruptTimer();\n+\t\tis = new TimeoutInputStream(in, timer);\n+\t\tis.setTimeout(timeout);\n+\t}\n+\n+\tprotected void tearDown() throws Exception {\n+\t\tclose();\n+\t\tsuper.tearDown();\n+\t}\n+\n+\tpublic void testTimeout_readByte_Success1() throws IOException {\n+\t\tout.write('a');\n+\t\tassertEquals('a', is.read());\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_readByte_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tout.write(exp);\n+\t\tassertEquals(exp[0], is.read());\n+\t\tassertEquals(exp[1], is.read());\n+\t\tassertEquals(exp[2], is.read());\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_readByte_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tis.read();\n+\t\t\tfail(\"incorrectly read a byte\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Success1() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tout.write(exp);\n+\t\tNB.readFully(is, act, 0, act.length);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tout.write(exp);\n+\t\tNB.readFully(is, act, 0, 1);\n+\t\tNB.readFully(is, act, 1, 1);\n+\t\tNB.readFully(is, act, 2, 1);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tis.read(new byte[512]);\n+\t\t\tfail(\"incorrectly read bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_skip_Success() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tout.write(exp);\n+\t\tassertEquals(2, is.skip(2));\n+\t\tassertEquals('c', is.read());\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_skip_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tis.skip(1024);\n+\t\t\tfail(\"incorrectly skipped bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tclose();\n+\t}\n+\n+\tprivate void beginRead() {\n+\t\tstart = now();\n+\t}\n+\n+\tprivate void assertTimeout() {\n+\t\tfinal long wait = now() - start;\n+\t\tassertTrue(Math.abs(wait - timeout) < 50);\n+\t}\n+\n+\tprivate void close() {\n+\t\ttimer.terminate();\n+\t\tfor (Thread t : active())\n+\t\t\tassertFalse(t instanceof InterruptTimer.AlarmThread);\n+\t}\n+\n+\tprivate static List<Thread> active() {\n+\t\tThread[] all = new Thread[16];\n+\t\tint n = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\twhile (n == all.length) {\n+\t\t\tall = new Thread[all.length * 2];\n+\t\t\tn = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\t}\n+\t\treturn Arrays.asList(all).subList(0, n);\n+\t}\n+\n+\tprivate static long now() {\n+\t\treturn System.currentTimeMillis();\n+\t}\n+}\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\nnew file mode 100644\nindex 0000000..4b0656d\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n@@ -0,0 +1,286 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.OutputStream;\n+import java.io.PipedInputStream;\n+import java.io.PipedOutputStream;\n+import java.util.Arrays;\n+import java.util.List;\n+\n+import org.spearce.jgit.util.NB;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutOutputStream;\n+\n+import junit.framework.TestCase;\n+\n+public class TimeoutOutputStreamTest extends TestCase {\n+\tprivate static final int timeout = 250;\n+\n+\tprivate PipedOutputStream out;\n+\n+\tprivate FullPipeInputStream in;\n+\n+\tprivate InterruptTimer timer;\n+\n+\tprivate TimeoutOutputStream os;\n+\n+\tprivate long start;\n+\n+\tprotected void setUp() throws Exception {\n+\t\tsuper.setUp();\n+\t\tout = new PipedOutputStream();\n+\t\tin = new FullPipeInputStream(out);\n+\t\ttimer = new InterruptTimer();\n+\t\tos = new TimeoutOutputStream(out, timer);\n+\t\tos.setTimeout(timeout);\n+\t}\n+\n+\tprotected void tearDown() throws Exception {\n+\t\tclose();\n+\t\tsuper.tearDown();\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Success1() throws IOException {\n+\t\tin.free(1);\n+\t\tos.write('a');\n+\t\tin.want(1);\n+\t\tassertEquals('a', in.read());\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tin.free(exp.length);\n+\t\tos.write(exp[0]);\n+\t\tos.write(exp[1]);\n+\t\tos.write(exp[2]);\n+\t\tin.want(exp.length);\n+\t\tin.read(act);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Timeout() throws IOException {\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.write('\\n');\n+\t\t\tfail(\"incorrectly write a byte\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_writeBuffer_Success1() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tin.free(exp.length);\n+\t\tos.write(exp);\n+\t\tin.want(exp.length);\n+\t\tin.read(act);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_writeBuffer_Timeout() throws IOException {\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.write(new byte[512]);\n+\t\t\tfail(\"incorrectly wrote bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_flush_Success() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void flush() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\t\tos.flush();\n+\t\tassertTrue(called[0]);\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_flush_Timeout() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void flush() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tThread.sleep(1000);\n+\t\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t\tthrow new InterruptedIOException();\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.flush();\n+\t\t\tfail(\"incorrectly flushed\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tassertTrue(called[0]);\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_close_Success() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void close() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\t\tos.close();\n+\t\tassertTrue(called[0]);\n+\t\tclose();\n+\t}\n+\n+\tpublic void testTimeout_close_Timeout() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void close() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tThread.sleep(1000);\n+\t\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t\tthrow new InterruptedIOException();\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.close();\n+\t\t\tfail(\"incorrectly closed\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tassertTrue(called[0]);\n+\t\tclose();\n+\t}\n+\n+\tprivate void beginWrite() {\n+\t\tstart = now();\n+\t}\n+\n+\tprivate void assertTimeout() {\n+\t\tfinal long wait = now() - start;\n+\t\tassertTrue(Math.abs(wait - timeout) < 50);\n+\t}\n+\n+\tprivate void close() {\n+\t\ttimer.terminate();\n+\t\tfor (Thread t : active())\n+\t\t\tassertFalse(t instanceof InterruptTimer.AlarmThread);\n+\t}\n+\n+\tprivate static List<Thread> active() {\n+\t\tThread[] all = new Thread[16];\n+\t\tint n = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\twhile (n == all.length) {\n+\t\t\tall = new Thread[all.length * 2];\n+\t\t\tn = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\t}\n+\t\treturn Arrays.asList(all).subList(0, n);\n+\t}\n+\n+\tprivate static long now() {\n+\t\treturn System.currentTimeMillis();\n+\t}\n+\n+\tprivate final class FullPipeInputStream extends PipedInputStream {\n+\t\tFullPipeInputStream(PipedOutputStream src) throws IOException {\n+\t\t\tsuper(src);\n+\t\t\tsrc.write(new byte[PIPE_SIZE]);\n+\t\t}\n+\n+\t\tvoid want(int cnt) throws IOException {\n+\t\t\tNB.skipFully(this, PIPE_SIZE - cnt);\n+\t\t}\n+\n+\t\tvoid free(int cnt) throws IOException {\n+\t\t\tNB.skipFully(this, cnt);\n+\t\t}\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\nnew file mode 100644\nindex 0000000..8f625e0\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n@@ -0,0 +1,216 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+/**\n+ * Triggers an interrupt on the calling thread if it doesn't complete a block.\n+ * <p>\n+ * Classes can use this to trip an alarm interrupting the calling thread if it\n+ * doesn't complete a block within the specified timeout. Typical calling\n+ * pattern is:\n+ *\n+ * <pre>\n+ * private InterruptTimer myTimer = ...;\n+ * void foo() {\n+ *   try {\n+ *     myTimer.begin(timeout);\n+ *     // work\n+ *   } finally {\n+ *     myTimer.end();\n+ *   }\n+ * }\n+ * </pre>\n+ * <p>\n+ * An InterruptTimer is not recursive. To implement recursive timers,\n+ * independent InterruptTimer instances are required. A single InterruptTimer\n+ * may be shared between objects which won't recursively call each other.\n+ * <p>\n+ * Each InterruptTimer spawns one background thread to sleep the specified time\n+ * and interrupt the thread which called {@link #begin(int)}. It is up to the\n+ * caller to ensure that the operations within the work block between the\n+ * matched begin and end calls tests the interrupt flag (most IO operations do).\n+ * <p>\n+ * To terminate the background thread, use {@link #terminate()}. If the\n+ * application fails to terminate the thread, it will (eventually) terminate\n+ * itself when the InterruptTimer instance is garbage collected.\n+ *\n+ * @see TimeoutInputStream\n+ */\n+public final class InterruptTimer {\n+\tprivate final AlarmState state;\n+\n+\tprivate final AlarmThread thread;\n+\n+\tfinal AutoKiller autoKiller;\n+\n+\t/** Create a new timer with a default thread name. */\n+\tpublic InterruptTimer() {\n+\t\tthis(\"JGit-InterruptTimer\");\n+\t}\n+\n+\t/**\n+\t * Create a new timer to signal on interrupt on the caller.\n+\t * <p>\n+\t * The timer thread is created in the calling thread's ThreadGroup.\n+\t *\n+\t * @param threadName\n+\t *            name of the timer thread.\n+\t */\n+\tpublic InterruptTimer(final String threadName) {\n+\t\tstate = new AlarmState();\n+\t\tautoKiller = new AutoKiller(state);\n+\t\tthread = new AlarmThread(threadName, state);\n+\t\tthread.start();\n+\t}\n+\n+\t/**\n+\t * Arm the interrupt timer before entering a blocking operation.\n+\t *\n+\t * @param timeout\n+\t *            number of milliseconds before the interrupt should trigger.\n+\t *            Must be > 0.\n+\t */\n+\tpublic void begin(final int timeout) {\n+\t\tif (timeout <= 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + timeout);\n+\t\tThread.interrupted();\n+\t\tstate.begin(timeout);\n+\t}\n+\n+\t/** Disable the interrupt timer, as the operation is complete. */\n+\tpublic void end() {\n+\t\tstate.end();\n+\t}\n+\n+\t/** Shutdown the timer thread, and wait for it to terminate. */\n+\tpublic void terminate() {\n+\t\tstate.terminate();\n+\t\ttry {\n+\t\t\tthread.join();\n+\t\t} catch (InterruptedException e) {\n+\t\t\t//\n+\t\t}\n+\t}\n+\n+\tstatic final class AlarmThread extends Thread {\n+\t\tAlarmThread(final String name, final AlarmState q) {\n+\t\t\tsuper(q);\n+\t\t\tsetName(name);\n+\t\t\tsetDaemon(true);\n+\t\t}\n+\t}\n+\n+\t// The trick here is, the AlarmThread does not have a reference to the\n+\t// AutoKiller instance, only the InterruptTimer itself does. Thus when\n+\t// the InterruptTimer is GC'd, the AutoKiller is also unreachable and\n+\t// can be GC'd. When it gets finalized, it tells the AlarmThread to\n+\t// terminate, triggering the thread to exit gracefully.\n+\t//\n+\tprivate static final class AutoKiller {\n+\t\tprivate final AlarmState state;\n+\n+\t\tAutoKiller(final AlarmState s) {\n+\t\t\tstate = s;\n+\t\t}\n+\n+\t\t@Override\n+\t\tprotected void finalize() throws Throwable {\n+\t\t\tstate.terminate();\n+\t\t}\n+\t}\n+\n+\tstatic final class AlarmState implements Runnable {\n+\t\tprivate Thread callingThread;\n+\n+\t\tprivate long deadline;\n+\n+\t\tprivate boolean terminated;\n+\n+\t\tAlarmState() {\n+\t\t\tcallingThread = Thread.currentThread();\n+\t\t}\n+\n+\t\tpublic synchronized void run() {\n+\t\t\twhile (!terminated && callingThread.isAlive()) {\n+\t\t\t\ttry {\n+\t\t\t\t\tif (0 < deadline) {\n+\t\t\t\t\t\tfinal long delay = deadline - now();\n+\t\t\t\t\t\tif (delay <= 0) {\n+\t\t\t\t\t\t\tdeadline = 0;\n+\t\t\t\t\t\t\tcallingThread.interrupt();\n+\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\twait(delay);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\twait(1000);\n+\t\t\t\t\t}\n+\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t// Treat an interrupt as notice to examine state.\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tsynchronized void begin(final int timeout) {\n+\t\t\tif (terminated)\n+\t\t\t\tthrow new IllegalStateException(\"Timer already terminated\");\n+\t\t\tcallingThread = Thread.currentThread();\n+\t\t\tdeadline = now() + timeout;\n+\t\t\tnotifyAll();\n+\t\t}\n+\n+\t\tsynchronized void end() {\n+\t\t\tif (0 == deadline)\n+\t\t\t\tThread.interrupted();\n+\t\t\telse\n+\t\t\t\tdeadline = 0;\n+\t\t\tnotifyAll();\n+\t\t}\n+\n+\t\tsynchronized void terminate() {\n+\t\t\tif (!terminated) {\n+\t\t\t\tdeadline = 0;\n+\t\t\t\tterminated = true;\n+\t\t\t\tnotifyAll();\n+\t\t\t}\n+\t\t}\n+\n+\t\tprivate static long now() {\n+\t\t\treturn System.currentTimeMillis();\n+\t\t}\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\nnew file mode 100644\nindex 0000000..3a321aa\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n@@ -0,0 +1,133 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.FilterInputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.InterruptedIOException;\n+\n+/** InputStream with a configurable timeout. */\n+public class TimeoutInputStream extends FilterInputStream {\n+\tprivate final InterruptTimer myTimer;\n+\n+\tprivate int timeout;\n+\n+\t/**\n+\t * Wrap an input stream with a timeout on all read operations.\n+\t *\n+\t * @param src\n+\t *            base input stream (to read from). The stream must be\n+\t *            interruptible (most socket streams are).\n+\t * @param timer\n+\t *            timer to manage the timeouts during reads.\n+\t */\n+\tpublic TimeoutInputStream(final InputStream src,\n+\t\t\tfinal InterruptTimer timer) {\n+\t\tsuper(src);\n+\t\tmyTimer = timer;\n+\t}\n+\n+\t/** @return number of milliseconds before aborting a read. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * @param millis\n+\t *            number of milliseconds before aborting a read. Must be > 0.\n+\t */\n+\tpublic void setTimeout(final int millis) {\n+\t\tif (millis < 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + millis);\n+\t\ttimeout = millis;\n+\t}\n+\n+\t@Override\n+\tpublic int read() throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.read();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic int read(byte[] buf) throws IOException {\n+\t\treturn read(buf, 0, buf.length);\n+\t}\n+\n+\t@Override\n+\tpublic int read(byte[] buf, int off, int cnt) throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.read(buf, off, cnt);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic long skip(long cnt) throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.skip(cnt);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\tprivate void beginRead() {\n+\t\tmyTimer.begin(timeout);\n+\t}\n+\n+\tprivate void endRead() {\n+\t\tmyTimer.end();\n+\t}\n+\n+\tprivate static InterruptedIOException readTimedOut() {\n+\t\treturn new InterruptedIOException(\"Read timed out\");\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\nnew file mode 100644\nindex 0000000..014cd92\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n@@ -0,0 +1,146 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.OutputStream;\n+\n+/** OutputStream with a configurable timeout. */\n+public class TimeoutOutputStream extends OutputStream {\n+\tprivate final OutputStream dst;\n+\n+\tprivate final InterruptTimer myTimer;\n+\n+\tprivate int timeout;\n+\n+\t/**\n+\t * Wrap an output stream with a timeout on all write operations.\n+\t *\n+\t * @param destination\n+\t *            base input stream (to write to). The stream must be\n+\t *            interruptible (most socket streams are).\n+\t * @param timer\n+\t *            timer to manage the timeouts during writes.\n+\t */\n+\tpublic TimeoutOutputStream(final OutputStream destination,\n+\t\t\tfinal InterruptTimer timer) {\n+\t\tdst = destination;\n+\t\tmyTimer = timer;\n+\t}\n+\n+\t/** @return number of milliseconds before aborting a write. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * @param millis\n+\t *            number of milliseconds before aborting a write. Must be > 0.\n+\t */\n+\tpublic void setTimeout(final int millis) {\n+\t\tif (millis < 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + millis);\n+\t\ttimeout = millis;\n+\t}\n+\n+\t@Override\n+\tpublic void write(int b) throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.write(b);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void write(byte[] buf) throws IOException {\n+\t\twrite(buf, 0, buf.length);\n+\t}\n+\n+\t@Override\n+\tpublic void write(byte[] buf, int off, int len) throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.write(buf, off, len);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void flush() throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.flush();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void close() throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.close();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\tprivate void beginWrite() {\n+\t\tmyTimer.begin(timeout);\n+\t}\n+\n+\tprivate void endWrite() {\n+\t\tmyTimer.end();\n+\t}\n+\n+\tprivate static InterruptedIOException writeTimedOut() {\n+\t\treturn new InterruptedIOException(\"Write timed out\");\n+\t}\n+}\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116642","messageId":"1245446875-31102-3-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 2/6] Add remote.name.timeout to configure an IO timeout","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:51Z","receivedAt":"2009-06-19T21:27:51Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"An IO timeout can be useful if the remote peer stops responding,\nand the user wants the application to abort rather than block\nindefinitely waiting for more input.\n\nThis is a JGit specific extension to the standard remote format.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../spearce/jgit/transport/RemoteConfigTest.java   |   26 ++++++++++++++++\n .../org/spearce/jgit/transport/RemoteConfig.java   |   31 ++++++++++++++++++++\n 2 files changed, 57 insertions(+), 0 deletions(-)\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\nindex 6b72b64..3965bdb 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\n@@ -78,6 +78,7 @@ writeConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\tassertNotNull(rc.getFetchRefSpecs());\n \t\tassertNotNull(rc.getPushRefSpecs());\n \t\tassertNotNull(rc.getTagOpt());\n+\t\tassertEquals(0, rc.getTimeout());\n \t\tassertSame(TagOpt.AUTO_FOLLOW, rc.getTagOpt());\n \n \t\tassertEquals(1, allURIs.size());\n@@ -423,4 +424,29 @@ checkFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\"\n \t\t\t\t+ \"\\ttagopt = --tags\\n\");\n \t}\n+\n+\tpublic void testSimpleTimeout() throws Exception {\n+\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n+\t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\"\n+\t\t\t\t+ \"timeout = 12\\n\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tassertEquals(12, rc.getTimeout());\n+\t}\n+\n+\tpublic void testSaveTimeout() throws Exception {\n+\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"origin\");\n+\t\trc.addURI(new URIish(\"/some/dir\"));\n+\t\trc.addFetchRefSpec(new RefSpec(\"+refs/heads/*:refs/remotes/\"\n+\t\t\t\t+ rc.getName() + \"/*\"));\n+\t\trc.setTimeout(60);\n+\t\trc.update(db.getConfig());\n+\t\tdb.getConfig().save();\n+\n+\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n+\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n+\t\t\t\t+ \"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\"\n+\t\t\t\t+ \"\\ttimeout = 60\\n\");\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\nindex 519a8a5..a621dc4 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\n@@ -70,6 +70,8 @@\n \n \tprivate static final String KEY_MIRROR = \"mirror\";\n \n+\tprivate static final String KEY_TIMEOUT = \"timeout\";\n+\n \tprivate static final boolean DEFAULT_MIRROR = false;\n \n \t/** Default value for {@link #getUploadPack()} if not specified. */\n@@ -120,6 +122,8 @@\n \n \tprivate boolean mirror;\n \n+\tprivate int timeout;\n+\n \t/**\n \t * Parse a remote block from an existing configuration file.\n \t * <p>\n@@ -170,6 +174,7 @@ public RemoteConfig(final RepositoryConfig rc, final String remoteName)\n \t\tval = rc.getString(SECTION, name, KEY_TAGOPT);\n \t\ttagopt = TagOpt.fromOption(val);\n \t\tmirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR);\n+\t\ttimeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0);\n \t}\n \n \t/**\n@@ -200,6 +205,7 @@ public void update(final RepositoryConfig rc) {\n \t\tset(rc, KEY_RECEIVEPACK, getReceivePack(), DEFAULT_RECEIVE_PACK);\n \t\tset(rc, KEY_TAGOPT, getTagOpt().option(), TagOpt.AUTO_FOLLOW.option());\n \t\tset(rc, KEY_MIRROR, mirror, DEFAULT_MIRROR);\n+\t\tset(rc, KEY_TIMEOUT, timeout, 0);\n \t}\n \n \tprivate void set(final RepositoryConfig rc, final String key,\n@@ -218,6 +224,14 @@ private void set(final RepositoryConfig rc, final String key,\n \t\t\trc.setBoolean(SECTION, getName(), key, currentValue);\n \t}\n \n+\tprivate void set(final RepositoryConfig rc, final String key,\n+\t\t\tfinal int currentValue, final int defaultValue) {\n+\t\tif (defaultValue == currentValue)\n+\t\t\tunset(rc, key);\n+\t\telse\n+\t\t\trc.setInt(SECTION, getName(), key, currentValue);\n+\t}\n+\n \tprivate void unset(final RepositoryConfig rc, final String key) {\n \t\trc.unsetString(SECTION, getName(), key);\n \t}\n@@ -420,4 +434,21 @@ public boolean isMirror() {\n \tpublic void setMirror(final boolean m) {\n \t\tmirror = m;\n \t}\n+\n+\t/** @return timeout (in seconds) before aborting an IO operation. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * Set the timeout before willing to abort an IO call.\n+\t *\n+\t * @param seconds\n+\t *            number of seconds to wait (with no data transfer occurring)\n+\t *            before aborting an IO read or write operation with this\n+\t *            remote.  A timeout of 0 will block indefinitely.\n+\t */\n+\tpublic void setTimeout(final int seconds) {\n+\t\ttimeout = seconds;\n+\t}\n }\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116640","messageId":"1245446875-31102-4-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-3-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 3/6] Add timeouts to smart transport protocol clients","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:52Z","receivedAt":"2009-06-19T21:27:52Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"For both directions (input and output) we start a background thread\nto interrupt the main processing thread if a read or write event on\nthe low-level stream does not complete in the configured timeout.\nOn timeout, we abort the transaction entirely.  Any sort of short\nread or write doesn't need to be handled.\n\nJSch made a timeout on write difficult because they explicitly do\na catch for InterruptedException inside of their OutputStream.  We\nhave to work around that by creating an additional thread that just\nshuttles data between our own OutputStream and the real JSch stream.\nOur OutputStream can be interrupted, which causes it to close, which\nin turn closes the JSch Channel and Session.  That breaks our little\ncopy thread out of the JSch OutputStream, letting it terminate.  Its\nnot pretty, but its the best we can do with current versions of JSch.\n\nDuring a push receive-pack stalls while it processes the deltas in\nthe received data and indexes the new pack on disk, or unpacks the\ndata into loose objects.  During this stall we receive no network\ntransfer from the remote side, so we cannot know if the peer has\ndeadlocked with us, or is progressing normally.  Since the time is\nusually proportional to the data sent we give the remote side up to\n10x the time we spent transmitting data, or our configured timeout,\nhoping it will be able to complete and return a pack success or fail\nstatus report before we timeout.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../spearce/jgit/transport/BasePackConnection.java |   36 +++++++-\n .../jgit/transport/BasePackPushConnection.java     |   29 ++++++-\n .../org/spearce/jgit/transport/SshTransport.java   |    3 +-\n .../src/org/spearce/jgit/transport/Transport.java  |   21 ++++\n .../spearce/jgit/transport/TransportGitAnon.java   |   13 +++-\n .../spearce/jgit/transport/TransportGitSsh.java    |  103 +++++++++++++++++++-\n .../org/spearce/jgit/transport/TransportLocal.java |    9 ++-\n .../org/spearce/jgit/transport/TransportSftp.java  |    3 +-\n 8 files changed, 207 insertions(+), 10 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java\nindex 0382d2b..d759fc8 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackConnection.java\n@@ -55,6 +55,9 @@\n import org.spearce.jgit.lib.ObjectId;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutInputStream;\n+import org.spearce.jgit.util.io.TimeoutOutputStream;\n \n /**\n  * Base helper class for pack-based operations implementations. Provides partial\n@@ -75,6 +78,15 @@\n \t/** A transport connected to {@link #uri}. */\n \tprotected final Transport transport;\n \n+\t/** Low-level input stream, if a timeout was configured. */\n+\tprotected TimeoutInputStream timeoutIn;\n+\n+\t/** Low-level output stream, if a timeout was configured. */\n+\tprotected TimeoutOutputStream timeoutOut;\n+\n+\t/** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */\n+\tprivate InterruptTimer myTimer;\n+\n \t/** Buffered input stream reading from the remote. */\n \tprotected InputStream in;\n \n@@ -102,7 +114,19 @@ BasePackConnection(final PackTransport packTransport) {\n \t\turi = transport.uri;\n \t}\n \n-\tprotected void init(final InputStream myIn, final OutputStream myOut) {\n+\tprotected final void init(InputStream myIn, OutputStream myOut) {\n+\t\tfinal int timeout = transport.getTimeout();\n+\t\tif (timeout > 0) {\n+\t\t\tfinal Thread caller = Thread.currentThread();\n+\t\t\tmyTimer = new InterruptTimer(caller.getName() + \"-Timer\");\n+\t\t\ttimeoutIn = new TimeoutInputStream(myIn, myTimer);\n+\t\t\ttimeoutOut = new TimeoutOutputStream(myOut, myTimer);\n+\t\t\ttimeoutIn.setTimeout(timeout * 1000);\n+\t\t\ttimeoutOut.setTimeout(timeout * 1000);\n+\t\t\tmyIn = timeoutIn;\n+\t\t\tmyOut = timeoutOut;\n+\t\t}\n+\n \t\tin = myIn instanceof BufferedInputStream ? myIn\n \t\t\t\t: new BufferedInputStream(myIn, IndexPack.BUFFER_SIZE);\n \t\tout = myOut instanceof BufferedOutputStream ? myOut\n@@ -241,5 +265,15 @@ public void close() {\n \t\t\t\tpckIn = null;\n \t\t\t}\n \t\t}\n+\n+\t\tif (myTimer != null) {\n+\t\t\ttry {\n+\t\t\t\tmyTimer.terminate();\n+\t\t\t} finally {\n+\t\t\t\tmyTimer = null;\n+\t\t\t\ttimeoutIn = null;\n+\t\t\t\ttimeoutOut = null;\n+\t\t\t}\n+\t\t}\n \t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java\nindex 712d3c0..2a94dfc 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackPushConnection.java\n@@ -92,6 +92,9 @@\n \n \tprivate boolean writePack;\n \n+\t/** Time in milliseconds spent transferring the pack data. */\n+\tprivate long packTransferTime;\n+\n \tBasePackPushConnection(final PackTransport packTransport) {\n \t\tsuper(packTransport);\n \t\tthinPack = transport.isPushThin();\n@@ -209,12 +212,14 @@ private void writePack(final Map<String, RemoteRefUpdate> refUpdates,\n \t\twriter.setThin(thinPack);\n \t\twriter.setDeltaBaseAsOffset(capableOfsDelta);\n \t\twriter.preparePack(newObjects, remoteObjects);\n+\t\tfinal long start = System.currentTimeMillis();\n \t\twriter.writePack(out);\n+\t\tpackTransferTime = System.currentTimeMillis() - start;\n \t}\n \n \tprivate void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)\n \t\t\tthrows IOException {\n-\t\tfinal String unpackLine = pckIn.readString();\n+\t\tfinal String unpackLine = readStringLongTimeout();\n \t\tif (!unpackLine.startsWith(\"unpack \"))\n \t\t\tthrow new PackProtocolException(uri, \"unexpected report line: \"\n \t\t\t\t\t+ unpackLine);\n@@ -260,4 +265,26 @@ private void readStatusReport(final Map<String, RemoteRefUpdate> refUpdates)\n \t\t\t\t\t\t+ \" not received\");\n \t\t}\n \t}\n+\n+\tprivate String readStringLongTimeout() throws IOException {\n+\t\tif (timeoutIn == null)\n+\t\t\treturn pckIn.readString();\n+\n+\t\t// The remote side may need a lot of time to choke down the pack\n+\t\t// we just sent them. There may be many deltas that need to be\n+\t\t// resolved by the remote. Its hard to say how long the other\n+\t\t// end is going to be silent. Taking 10x the configured timeout\n+\t\t// or the time spent transferring the pack, whichever is larger,\n+\t\t// gives the other side some reasonable window to process the data,\n+\t\t// but this is just a wild guess.\n+\t\t//\n+\t\tfinal int oldTimeout = timeoutIn.getTimeout();\n+\t\tfinal int sendTime = (int) Math.min(packTransferTime, 28800000L);\n+\t\ttry {\n+\t\t\ttimeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout));\n+\t\t\treturn pckIn.readString();\n+\t\t} finally {\n+\t\t\ttimeoutIn.setTimeout(oldTimeout);\n+\t\t}\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SshTransport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SshTransport.java\nindex 127096c..d45a83f 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/SshTransport.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SshTransport.java\n@@ -111,6 +111,7 @@ protected void initSession() throws TransportException {\n \t\tif (sock != null)\n \t\t\treturn;\n \n+\t\tfinal int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;\n \t\tfinal String user = uri.getUser();\n \t\tfinal String pass = uri.getPass();\n \t\tfinal String host = uri.getHost();\n@@ -118,7 +119,7 @@ protected void initSession() throws TransportException {\n \t\ttry {\n \t\t\tsock = sch.getSession(user, pass, host, port);\n \t\t\tif (!sock.isConnected())\n-\t\t\t\tsock.connect();\n+\t\t\t\tsock.connect(tms);\n \t\t} catch (JSchException je) {\n \t\t\tfinal Throwable c = je.getCause();\n \t\t\tif (c instanceof UnknownHostException)\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java\nindex 1068f50..a6210a5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Transport.java\n@@ -376,6 +376,9 @@ private static String findTrackingRefName(final String remoteName,\n \t/** Should refs no longer on the source be pruned from the destination? */\n \tprivate boolean removeDeletedRefs;\n \n+\t/** Timeout in seconds to wait before aborting an IO read or write. */\n+\tprivate int timeout;\n+\n \t/**\n \t * Create a new transport instance.\n \t * \n@@ -572,6 +575,7 @@ public void applyConfig(final RemoteConfig cfg) {\n \t\tsetTagOpt(cfg.getTagOpt());\n \t\toptionReceivePack = cfg.getReceivePack();\n \t\tpush = cfg.getPushRefSpecs();\n+\t\ttimeout = cfg.getTimeout();\n \t}\n \n \t/**\n@@ -595,6 +599,23 @@ public void setDryRun(final boolean dryRun) {\n \t\tthis.dryRun = dryRun;\n \t}\n \n+\t/** @return timeout (in seconds) before aborting an IO operation. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * Set the timeout before willing to abort an IO call.\n+\t *\n+\t * @param seconds\n+\t *            number of seconds to wait (with no data transfer occurring)\n+\t *            before aborting an IO read or write operation with this\n+\t *            remote.\n+\t */\n+\tpublic void setTimeout(final int seconds) {\n+\t\ttimeout = seconds;\n+\t}\n+\n \t/**\n \t * Fetch objects and refs from the remote repository to the local one.\n \t * <p>\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\nindex 0c80b9d..e23fe3b 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitAnon.java\n@@ -42,6 +42,7 @@\n import java.io.IOException;\n import java.net.ConnectException;\n import java.net.InetAddress;\n+import java.net.InetSocketAddress;\n import java.net.Socket;\n import java.net.UnknownHostException;\n \n@@ -82,16 +83,26 @@ public void close() {\n \t}\n \n \tSocket openConnection() throws TransportException {\n+\t\tfinal int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;\n \t\tfinal int port = uri.getPort() > 0 ? uri.getPort() : GIT_PORT;\n+\t\tfinal Socket s = new Socket();\n \t\ttry {\n-\t\t\treturn new Socket(InetAddress.getByName(uri.getHost()), port);\n+\t\t\tfinal InetAddress host = InetAddress.getByName(uri.getHost());\n+\t\t\ts.bind(null);\n+\t\t\ts.connect(new InetSocketAddress(host, port), tms);\n \t\t} catch (IOException c) {\n+\t\t\ttry {\n+\t\t\t\ts.close();\n+\t\t\t} catch (IOException closeErr) {\n+\t\t\t\t// ignore a failure during close, we're already failing\n+\t\t\t}\n \t\t\tif (c instanceof UnknownHostException)\n \t\t\t\tthrow new TransportException(uri, \"unknown host\");\n \t\t\tif (c instanceof ConnectException)\n \t\t\t\tthrow new TransportException(uri, c.getMessage());\n \t\t\tthrow new TransportException(uri, c.getMessage(), c);\n \t\t}\n+\t\treturn s;\n \t}\n \n \tvoid service(final String name, final PacketLineOut pckOut)\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\nindex de72d02..bde641f 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportGitSsh.java\n@@ -40,7 +40,11 @@\n package org.spearce.jgit.transport;\n \n import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.InterruptedIOException;\n import java.io.OutputStream;\n+import java.io.PipedInputStream;\n+import java.io.PipedOutputStream;\n \n import org.spearce.jgit.errors.NoRemoteRepositoryException;\n import org.spearce.jgit.errors.TransportException;\n@@ -120,6 +124,7 @@ private static void sq(final StringBuilder cmd, final String val) {\n \tChannelExec exec(final String exe) throws TransportException {\n \t\tinitSession();\n \n+\t\tfinal int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;\n \t\ttry {\n \t\t\tfinal ChannelExec channel = (ChannelExec) sock.openChannel(\"exec\");\n \t\t\tString path = uri.getPath();\n@@ -139,7 +144,7 @@ ChannelExec exec(final String exe) throws TransportException {\n \t\t\tchannel.setCommand(cmd.toString());\n \t\t\terrStream = createErrorStream();\n \t\t\tchannel.setErrStream(errStream, true);\n-\t\t\tchannel.connect();\n+\t\t\tchannel.connect(tms);\n \t\t\treturn channel;\n \t\t} catch (JSchException je) {\n \t\t\tthrow new TransportException(uri, je.getMessage(), je);\n@@ -198,6 +203,98 @@ NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf) {\n \t\treturn new NoRemoteRepositoryException(uri, why);\n \t}\n \n+\t// JSch won't let us interrupt writes when we use our InterruptTimer to\n+\t// break out of a long-running write operation. To work around that we\n+\t// spawn a background thread to shuttle data through a pipe, as we can\n+\t// issue an interrupted write out of that. Its slower, so we only use\n+\t// this route if there is a timeout.\n+\t//\n+\tprivate OutputStream outputStream(ChannelExec channel) throws IOException {\n+\t\tfinal OutputStream out = channel.getOutputStream();\n+\t\tif (getTimeout() <= 0)\n+\t\t\treturn out;\n+\t\tfinal PipedInputStream pipeIn = new PipedInputStream();\n+\t\tfinal CopyThread copyThread = new CopyThread(pipeIn, out);\n+\t\tfinal PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) {\n+\t\t\t@Override\n+\t\t\tpublic void flush() throws IOException {\n+\t\t\t\tsuper.flush();\n+\t\t\t\tcopyThread.flush();\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void close() throws IOException {\n+\t\t\t\tsuper.close();\n+\t\t\t\ttry {\n+\t\t\t\t\tcopyThread.join(getTimeout() * 1000);\n+\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t// Just wake early, the thread will terminate anyway.\n+\t\t\t\t}\n+\t\t\t}\n+\t\t};\n+\t\tcopyThread.start();\n+\t\treturn pipeOut;\n+\t}\n+\n+\tprivate static class CopyThread extends Thread {\n+\t\tprivate final InputStream src;\n+\n+\t\tprivate final OutputStream dst;\n+\n+\t\tprivate volatile boolean doFlush;\n+\n+\t\tCopyThread(final InputStream i, final OutputStream o) {\n+\t\t\tsetName(Thread.currentThread().getName() + \"-Output\");\n+\t\t\tsrc = i;\n+\t\t\tdst = o;\n+\t\t}\n+\n+\t\tvoid flush() {\n+\t\t\tif (!doFlush) {\n+\t\t\t\tdoFlush = true;\n+\t\t\t\tinterrupt();\n+\t\t\t}\n+\t\t}\n+\n+\t\t@Override\n+\t\tpublic void run() {\n+\t\t\ttry {\n+\t\t\t\tfinal byte[] buf = new byte[1024];\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tif (doFlush) {\n+\t\t\t\t\t\t\tdoFlush = false;\n+\t\t\t\t\t\t\tdst.flush();\n+\t\t\t\t\t\t}\n+\n+\t\t\t\t\t\tfinal int n;\n+\t\t\t\t\t\ttry {\n+\t\t\t\t\t\t\tn = src.read(buf);\n+\t\t\t\t\t\t} catch (InterruptedIOException wakey) {\n+\t\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tif (n < 0)\n+\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\tdst.write(buf, 0, n);\n+\t\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t} finally {\n+\t\t\t\ttry {\n+\t\t\t\t\tsrc.close();\n+\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t// Ignore IO errors on close\n+\t\t\t\t}\n+\t\t\t\ttry {\n+\t\t\t\t\tdst.close();\n+\t\t\t\t} catch (IOException e) {\n+\t\t\t\t\t// Ignore IO errors on close\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tclass SshFetchConnection extends BasePackFetchConnection {\n \t\tprivate ChannelExec channel;\n \n@@ -207,7 +304,7 @@ SshFetchConnection() throws TransportException {\n \t\t\t\tchannel = exec(getOptionUploadPack());\n \n \t\t\t\tif (channel.isConnected())\n-\t\t\t\t\tinit(channel.getInputStream(), channel.getOutputStream());\n+\t\t\t\t\tinit(channel.getInputStream(), outputStream(channel));\n \t\t\t\telse\n \t\t\t\t\tthrow new TransportException(uri, errStream.toString());\n \n@@ -251,7 +348,7 @@ SshPushConnection() throws TransportException {\n \t\t\t\tchannel = exec(getOptionReceivePack());\n \n \t\t\t\tif (channel.isConnected())\n-\t\t\t\t\tinit(channel.getInputStream(), channel.getOutputStream());\n+\t\t\t\t\tinit(channel.getInputStream(), outputStream(channel));\n \t\t\t\telse\n \t\t\t\t\tthrow new TransportException(uri, errStream.toString());\n \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java\nindex 428f73e..8e8e8d5 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportLocal.java\n@@ -43,6 +43,7 @@\n import java.io.File;\n import java.io.IOException;\n import java.io.InputStream;\n+import java.io.OutputStream;\n import java.io.PipedInputStream;\n import java.io.PipedOutputStream;\n \n@@ -240,7 +241,9 @@ public void close() {\n \t\tForkLocalFetchConnection() throws TransportException {\n \t\t\tsuper(TransportLocal.this);\n \t\t\tuploadPack = startProcessWithErrStream(getOptionUploadPack());\n-\t\t\tinit(uploadPack.getInputStream(), uploadPack.getOutputStream());\n+\t\t\tfinal InputStream upIn = uploadPack.getInputStream();\n+\t\t\tfinal OutputStream upOut = uploadPack.getOutputStream();\n+\t\t\tinit(upIn, upOut);\n \t\t\treadAdvertisedRefs();\n \t\t}\n \n@@ -343,7 +346,9 @@ public void close() {\n \t\tForkLocalPushConnection() throws TransportException {\n \t\t\tsuper(TransportLocal.this);\n \t\t\treceivePack = startProcessWithErrStream(getOptionReceivePack());\n-\t\t\tinit(receivePack.getInputStream(), receivePack.getOutputStream());\n+\t\t\tfinal InputStream rpIn = receivePack.getInputStream();\n+\t\t\tfinal OutputStream rpOut = receivePack.getOutputStream();\n+\t\t\tinit(rpIn, rpOut);\n \t\t\treadAdvertisedRefs();\n \t\t}\n \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportSftp.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportSftp.java\nindex e18d128..57a6b09 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportSftp.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportSftp.java\n@@ -112,9 +112,10 @@ public PushConnection openPush() throws TransportException {\n \tChannelSftp newSftp() throws TransportException {\n \t\tinitSession();\n \n+\t\tfinal int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;\n \t\ttry {\n \t\t\tfinal Channel channel = sock.openChannel(\"sftp\");\n-\t\t\tchannel.connect();\n+\t\t\tchannel.connect(tms);\n \t\t\treturn (ChannelSftp) channel;\n \t\t} catch (JSchException je) {\n \t\t\tthrow new TransportException(uri, je.getMessage(), je);\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116643","messageId":"1245446875-31102-5-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-4-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 4/6] Add timeouts to smart transport protocol servers","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:53Z","receivedAt":"2009-06-19T21:27:53Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Like with the client side support, we spawn a background thread and\nuse that to wake up the real service thread if it blocks too long in\na read or write operation.  Typically this sort of long running IO\nindicates the client is not responding, and the server should abort\nits transaction and disconnect the client.\n\nLike with the push client, the push server doesn't know when the\nclient will be done computing its pack file and start sending it\nto the server.  Consequently we aren't entirely sure when we can\nsafely say the client is dead, vs. the client is just busy doing\nits local compression work before transmitting.  By upping our\ntimeout to 10x the originally configured value we can give the\nclient a reasonable chance to finish packing data before we do\nwind up aborting.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/ReceivePack.java    |   57 +++++++++++++++++++\n .../src/org/spearce/jgit/transport/UploadPack.java |   59 ++++++++++++++++++--\n 2 files changed, 110 insertions(+), 6 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\nindex c92a903..16b0c57 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -69,6 +69,9 @@\n import org.spearce.jgit.revwalk.RevObject;\n import org.spearce.jgit.revwalk.RevWalk;\n import org.spearce.jgit.transport.ReceiveCommand.Result;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutInputStream;\n+import org.spearce.jgit.util.io.TimeoutOutputStream;\n \n /**\n  * Implements the server side of a push connection, receiving objects.\n@@ -109,6 +112,14 @@\n \t/** Hook to report on the commands after execution. */\n \tprivate PostReceiveHook postReceive;\n \n+\t/** Timeout in seconds to wait for client interaction. */\n+\tprivate int timeout;\n+\n+\t/** Timer to manage {@link #timeout}. */\n+\tprivate InterruptTimer timer;\n+\n+\tprivate TimeoutInputStream timeoutIn;\n+\n \tprivate InputStream rawIn;\n \n \tprivate OutputStream rawOut;\n@@ -297,6 +308,23 @@ public void setPostReceiveHook(final PostReceiveHook h) {\n \t\tpostReceive = h != null ? h : PostReceiveHook.NULL;\n \t}\n \n+\t/** @return timeout (in seconds) before aborting an IO operation. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * Set the timeout before willing to abort an IO call.\n+\t *\n+\t * @param seconds\n+\t *            number of seconds to wait (with no data transfer occurring)\n+\t *            before aborting an IO read or write operation with the\n+\t *            connected client.\n+\t */\n+\tpublic void setTimeout(final int seconds) {\n+\t\ttimeout = seconds;\n+\t}\n+\n \t/** @return all of the command received by the current request. */\n \tpublic List<ReceiveCommand> getAllCommands() {\n \t\treturn Collections.unmodifiableList(commands);\n@@ -365,6 +393,17 @@ public void receive(final InputStream input, final OutputStream output,\n \t\t\trawIn = input;\n \t\t\trawOut = output;\n \n+\t\t\tif (timeout > 0) {\n+\t\t\t\tfinal Thread caller = Thread.currentThread();\n+\t\t\t\ttimer = new InterruptTimer(caller.getName() + \"-Timer\");\n+\t\t\t\ttimeoutIn = new TimeoutInputStream(rawIn, timer);\n+\t\t\t\tTimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);\n+\t\t\t\ttimeoutIn.setTimeout(timeout * 1000);\n+\t\t\t\to.setTimeout(timeout * 1000);\n+\t\t\t\trawIn = timeoutIn;\n+\t\t\t\trawOut = o;\n+\t\t\t}\n+\n \t\t\tpckIn = new PacketLineIn(rawIn);\n \t\t\tpckOut = new PacketLineOut(rawOut);\n \t\t\tif (messages != null) {\n@@ -389,6 +428,7 @@ public void println() {\n \t\t\t\t}\n \t\t\t} finally {\n \t\t\t\tunlockPack();\n+\t\t\t\ttimeoutIn = null;\n \t\t\t\trawIn = null;\n \t\t\t\trawOut = null;\n \t\t\t\tpckIn = null;\n@@ -397,6 +437,13 @@ public void println() {\n \t\t\t\trefs = null;\n \t\t\t\tenabledCapablities = null;\n \t\t\t\tcommands = null;\n+\t\t\t\tif (timer != null) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\ttimer.terminate();\n+\t\t\t\t\t} finally {\n+\t\t\t\t\t\ttimer = null;\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\n@@ -557,6 +604,13 @@ private boolean needPack() {\n \t}\n \n \tprivate void receivePack() throws IOException {\n+\t\t// It might take the client a while to pack the objects it needs\n+\t\t// to send to us.  We should increase our timeout so we don't\n+\t\t// abort while the client is computing.\n+\t\t//\n+\t\tif (timeoutIn != null)\n+\t\t\ttimeoutIn.setTimeout(10 * timeout * 1000);\n+\n \t\tfinal IndexPack ip = IndexPack.create(db, rawIn);\n \t\tip.setFixThin(true);\n \t\tip.setObjectChecking(isCheckReceivedObjects());\n@@ -566,6 +620,9 @@ private void receivePack() throws IOException {\n \t\tif (getRefLogIdent() != null)\n \t\t\tlockMsg += \" from \" + getRefLogIdent().toExternalString();\n \t\tpackLock = ip.renameAndOpenPack(lockMsg);\n+\n+\t\tif (timeoutIn != null)\n+\t\t\ttimeoutIn.setTimeout(timeout * 1000);\n \t}\n \n \tprivate void checkConnectivity() throws IOException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\nindex 7d17b2d..b0fa885 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/UploadPack.java\n@@ -64,6 +64,9 @@\n import org.spearce.jgit.revwalk.RevObject;\n import org.spearce.jgit.revwalk.RevTag;\n import org.spearce.jgit.revwalk.RevWalk;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutInputStream;\n+import org.spearce.jgit.util.io.TimeoutOutputStream;\n \n /**\n  * Implements the server side of a fetch connection, transmitting objects.\n@@ -89,6 +92,12 @@\n \t/** Revision traversal support over {@link #db}. */\n \tprivate final RevWalk walk;\n \n+\t/** Timeout in seconds to wait for client interaction. */\n+\tprivate int timeout;\n+\n+\t/** Timer to manage {@link #timeout}. */\n+\tprivate InterruptTimer timer;\n+\n \tprivate InputStream rawIn;\n \n \tprivate OutputStream rawOut;\n@@ -164,6 +173,23 @@ public final RevWalk getRevWalk() {\n \t\treturn walk;\n \t}\n \n+\t/** @return timeout (in seconds) before aborting an IO operation. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * Set the timeout before willing to abort an IO call.\n+\t *\n+\t * @param seconds\n+\t *            number of seconds to wait (with no data transfer occurring)\n+\t *            before aborting an IO read or write operation with the\n+\t *            connected client.\n+\t */\n+\tpublic void setTimeout(final int seconds) {\n+\t\ttimeout = seconds;\n+\t}\n+\n \t/**\n \t * Execute the upload task on the socket.\n \t *\n@@ -183,12 +209,33 @@ public final RevWalk getRevWalk() {\n \t */\n \tpublic void upload(final InputStream input, final OutputStream output,\n \t\t\tfinal OutputStream messages) throws IOException {\n-\t\trawIn = input;\n-\t\trawOut = output;\n+\t\ttry {\n+\t\t\trawIn = input;\n+\t\t\trawOut = output;\n+\n+\t\t\tif (timeout > 0) {\n+\t\t\t\tfinal Thread caller = Thread.currentThread();\n+\t\t\t\ttimer = new InterruptTimer(caller.getName() + \"-Timer\");\n+\t\t\t\tTimeoutInputStream i = new TimeoutInputStream(rawIn, timer);\n+\t\t\t\tTimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);\n+\t\t\t\ti.setTimeout(timeout * 1000);\n+\t\t\t\to.setTimeout(timeout * 1000);\n+\t\t\t\trawIn = i;\n+\t\t\t\trawOut = o;\n+\t\t\t}\n \n-\t\tpckIn = new PacketLineIn(rawIn);\n-\t\tpckOut = new PacketLineOut(rawOut);\n-\t\tservice();\n+\t\t\tpckIn = new PacketLineIn(rawIn);\n+\t\t\tpckOut = new PacketLineOut(rawOut);\n+\t\t\tservice();\n+\t\t} finally {\n+\t\t\tif (timer != null) {\n+\t\t\t\ttry {\n+\t\t\t\t\ttimer.terminate();\n+\t\t\t\t} finally {\n+\t\t\t\t\ttimer = null;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n \t}\n \n \tprivate void service() throws IOException {\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116645","messageId":"1245446875-31102-6-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-5-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 5/6] Add timeouts to anonymous git:// daemon","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:54Z","receivedAt":"2009-06-19T21:27:54Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If the initial command line isn't received within the configured\ntimeout period, the connection is dropped, and the service thread\nis able to terminate.\n\nThe timeout is also pushed down to the service implementations,\nso they can abort if the client doesn't speak to them within the\nconfigured time span.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/transport/Daemon.java     |   26 ++++++++++++++++---\n .../org/spearce/jgit/transport/DaemonClient.java   |   12 +++++++--\n 2 files changed, 31 insertions(+), 7 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\nindex be27732..3101d6f 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/Daemon.java\n@@ -37,8 +37,6 @@\n \n package org.spearce.jgit.transport;\n \n-import java.io.BufferedInputStream;\n-import java.io.BufferedOutputStream;\n import java.io.File;\n import java.io.IOException;\n import java.io.InputStream;\n@@ -79,6 +77,8 @@\n \n \tprivate Thread acceptThread;\n \n+\tprivate int timeout;\n+\n \t/** Configure a daemon to listen on any available network port. */\n \tpublic Daemon() {\n \t\tthis(null);\n@@ -108,6 +108,7 @@ protected void execute(final DaemonClient dc,\n \t\t\t\t\t\t\tfinal Repository db) throws IOException {\n \t\t\t\t\t\tfinal UploadPack rp = new UploadPack(db);\n \t\t\t\t\t\tfinal InputStream in = dc.getInputStream();\n+\t\t\t\t\t\trp.setTimeout(Daemon.this.getTimeout());\n \t\t\t\t\t\trp.upload(in, dc.getOutputStream(), null);\n \t\t\t\t\t}\n \t\t\t\t}, new DaemonService(\"receive-pack\", \"receivepack\") {\n@@ -127,6 +128,7 @@ protected void execute(final DaemonClient dc,\n \t\t\t\t\t\tfinal String name = \"anonymous\";\n \t\t\t\t\t\tfinal String email = name + \"@\" + host;\n \t\t\t\t\t\trp.setRefLogIdent(new PersonIdent(name, email));\n+\t\t\t\t\t\trp.setTimeout(Daemon.this.getTimeout());\n \t\t\t\t\t\trp.receive(in, dc.getOutputStream(), null);\n \t\t\t\t\t}\n \t\t\t\t} };\n@@ -213,6 +215,23 @@ synchronized (exportBase) {\n \t\t}\n \t}\n \n+\t/** @return timeout (in seconds) before aborting an IO operation. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * Set the timeout before willing to abort an IO call.\n+\t *\n+\t * @param seconds\n+\t *            number of seconds to wait (with no data transfer occurring)\n+\t *            before aborting an IO read or write operation with the\n+\t *            connected client.\n+\t */\n+\tpublic void setTimeout(final int seconds) {\n+\t\ttimeout = seconds;\n+\t}\n+\n \t/**\n \t * Start this daemon on a background thread.\n \t *\n@@ -280,8 +299,7 @@ private void startClient(final Socket s) {\n \t\tnew Thread(processors, \"Git-Daemon-Client \" + peer.toString()) {\n \t\t\tpublic void run() {\n \t\t\t\ttry {\n-\t\t\t\t\tdc.execute(new BufferedInputStream(s.getInputStream()),\n-\t\t\t\t\t\t\tnew BufferedOutputStream(s.getOutputStream()));\n+\t\t\t\t\tdc.execute(s);\n \t\t\t\t} catch (IOException e) {\n \t\t\t\t\t// Ignore unexpected IO exceptions from clients\n \t\t\t\t\te.printStackTrace();\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\nindex e80d86b..52b2ae0 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonClient.java\n@@ -37,10 +37,13 @@\n \n package org.spearce.jgit.transport;\n \n+import java.io.BufferedInputStream;\n+import java.io.BufferedOutputStream;\n import java.io.IOException;\n import java.io.InputStream;\n import java.io.OutputStream;\n import java.net.InetAddress;\n+import java.net.Socket;\n \n /** Active network client of {@link Daemon}. */\n public class DaemonClient {\n@@ -80,11 +83,13 @@ public OutputStream getOutputStream() {\n \t\treturn rawOut;\n \t}\n \n-\tvoid execute(final InputStream in, final OutputStream out)\n+\tvoid execute(final Socket sock)\n \t\t\tthrows IOException {\n-\t\trawIn = in;\n-\t\trawOut = out;\n+\t\trawIn = new BufferedInputStream(sock.getInputStream());\n+\t\trawOut = new BufferedOutputStream(sock.getOutputStream());\n \n+\t\tif (0 < daemon.getTimeout())\n+\t\t\tsock.setSoTimeout(daemon.getTimeout() * 1000);\n \t\tString cmd = new PacketLineIn(rawIn).readStringRaw();\n \t\tfinal int nul = cmd.indexOf('\\0');\n \t\tif (nul >= 0) {\n@@ -98,6 +103,7 @@ void execute(final InputStream in, final OutputStream out)\n \t\tfinal DaemonService srv = getDaemon().matchService(cmd);\n \t\tif (srv == null)\n \t\t\treturn;\n+\t\tsock.setSoTimeout(0);\n \t\tsrv.execute(this, cmd);\n \t}\n }\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116644","messageId":"1245446875-31102-7-git-send-email-spearce@spearce.org","threadId":"19868","inReplyTo":"1245446875-31102-6-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 6/6] Add --timeout command line options","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-19T21:27:55Z","receivedAt":"2009-06-19T21:27:55Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/pgm/Daemon.java           |    5 +++++\n .../src/org/spearce/jgit/pgm/Fetch.java            |    5 +++++\n .../src/org/spearce/jgit/pgm/LsRemote.java         |    6 ++++++\n .../src/org/spearce/jgit/pgm/Push.java             |    5 +++++\n .../src/org/spearce/jgit/pgm/UploadPack.java       |    6 ++++++\n 5 files changed, 27 insertions(+), 0 deletions(-)\n\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\nindex e064fd8..6508910 100644\n--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Daemon.java\n@@ -54,6 +54,9 @@\n \t@Option(name = \"--listen\", metaVar = \"HOSTNAME\", usage = \"hostname (or ip) to listen on\")\n \tString host;\n \n+\t@Option(name = \"--timeout\", metaVar = \"SECONDS\", usage = \"abort connection if no activity\")\n+\tint timeout = -1;\n+\n \t@Option(name = \"--enable\", metaVar = \"SERVICE\", usage = \"enable the service in all repositories\", multiValued = true)\n \tfinal List<String> enable = new ArrayList<String>();\n \n@@ -85,6 +88,8 @@ protected void run() throws Exception {\n \t\t\t\thost != null ? new InetSocketAddress(host, port)\n \t\t\t\t\t\t: new InetSocketAddress(port));\n \t\td.setExportAll(exportAll);\n+\t\tif (0 <= timeout)\n+\t\t\td.setTimeout(timeout);\n \n \t\tfor (final String n : enable)\n \t\t\tservice(d, n).setEnabled(true);\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java\nindex 81d6893..d7be9fa 100644\n--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Fetch.java\n@@ -48,6 +48,9 @@\n \n @Command(common = true, usage = \"Update remote refs from another repository\")\n class Fetch extends AbstractFetchCommand {\n+\t@Option(name = \"--timeout\", metaVar = \"SECONDS\", usage = \"abort connection if no activity\")\n+\tint timeout = -1;\n+\n \t@Option(name = \"--fsck\", usage = \"perform fsck style checks on receive\")\n \tprivate Boolean fsck;\n \n@@ -86,6 +89,8 @@ protected void run() throws Exception {\n \t\ttn.setDryRun(dryRun);\n \t\tif (thin != null)\n \t\t\ttn.setFetchThin(thin.booleanValue());\n+\t\tif (0 <= timeout)\n+\t\t\ttn.setTimeout(timeout);\n \t\tfinal FetchResult r;\n \t\ttry {\n \t\t\tr = tn.fetch(new TextProgressMonitor(), toget);\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/LsRemote.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/LsRemote.java\nindex bfa38ec..da2de9a 100644\n--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/LsRemote.java\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/LsRemote.java\n@@ -38,18 +38,24 @@\n package org.spearce.jgit.pgm;\n \n import org.kohsuke.args4j.Argument;\n+import org.kohsuke.args4j.Option;\n import org.spearce.jgit.lib.AnyObjectId;\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.transport.FetchConnection;\n import org.spearce.jgit.transport.Transport;\n \n class LsRemote extends TextBuiltin {\n+\t@Option(name = \"--timeout\", metaVar = \"SECONDS\", usage = \"abort connection if no activity\")\n+\tint timeout = -1;\n+\n \t@Argument(index = 0, metaVar = \"uri-ish\", required = true)\n \tprivate String remote;\n \n \t@Override\n \tprotected void run() throws Exception {\n \t\tfinal Transport tn = Transport.open(db, remote);\n+\t\tif (0 <= timeout)\n+\t\t\ttn.setTimeout(timeout);\n \t\tfinal FetchConnection c = tn.openFetch();\n \t\ttry {\n \t\t\tfor (final Ref r : c.getRefs()) {\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java\nindex 19d31a1..018a521 100644\n--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Push.java\n@@ -55,6 +55,9 @@\n \n @Command(common = true, usage = \"Update remote repository from local refs\")\n class Push extends TextBuiltin {\n+\t@Option(name = \"--timeout\", metaVar = \"SECONDS\", usage = \"abort connection if no activity\")\n+\tint timeout = -1;\n+\n \t@Argument(index = 0, metaVar = \"uri-ish\")\n \tprivate String remote = \"origin\";\n \n@@ -104,6 +107,8 @@ protected void run() throws Exception {\n \n \t\tfinal List<Transport> transports = Transport.openAll(db, remote);\n \t\tfor (final Transport transport : transports) {\n+\t\t\tif (0 <= timeout)\n+\t\t\t\ttransport.setTimeout(timeout);\n \t\t\ttransport.setPushThin(thin);\n \t\t\tif (receivePack != null)\n \t\t\t\ttransport.setOptionReceivePack(receivePack);\ndiff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java\nindex d09d442..1d9af24 100644\n--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java\n+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/UploadPack.java\n@@ -40,10 +40,14 @@\n import java.io.File;\n \n import org.kohsuke.args4j.Argument;\n+import org.kohsuke.args4j.Option;\n import org.spearce.jgit.lib.Repository;\n \n @Command(common = false, usage = \"Server side backend for 'jgit fetch'\")\n class UploadPack extends TextBuiltin {\n+\t@Option(name = \"--timeout\", metaVar = \"SECONDS\", usage = \"abort connection if no activity\")\n+\tint timeout = -1;\n+\n \t@Argument(index = 0, required = true, metaVar = \"DIRECTORY\", usage = \"Repository to read from\")\n \tFile srcGitdir;\n \n@@ -62,6 +66,8 @@ protected void run() throws Exception {\n \t\tif (!db.getObjectsDirectory().isDirectory())\n \t\t\tthrow die(\"'\" + srcGitdir.getPath() + \"' not a git repository\");\n \t\trp = new org.spearce.jgit.transport.UploadPack(db);\n+\t\tif (0 <= timeout)\n+\t\t\trp.setTimeout(timeout);\n \t\trp.upload(System.in, System.out, System.err);\n \t}\n }\n-- \n1.6.3.2.416.g04d0\n"},{"id":"116684","messageId":"200906210028.48954.robin.rosenberg.lists@dewire.com","threadId":"19868","inReplyTo":"1245446875-31102-3-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 2/6] Add remote.name.timeout to configure an IO timeout","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-06-20T22:28:48Z","receivedAt":"2009-06-20T22:28:48Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"fredag 19 juni 2009 23:27:51 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> An IO timeout can be useful if the remote peer stops responding,\n> and the user wants the application to abort rather than block\n> indefinitely waiting for more input.\n> \n> This is a JGit specific extension to the standard remote format.\n\nCan we we assume C Git won't implement the same thing with a different \nparameter name, or worse, the same name, but a different unit?\n\n-- robin\n"},{"id":"116685","messageId":"20090620225404.GT11191@spearce.org","threadId":"19868","inReplyTo":"200906210028.48954.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 2/6] Add remote.name.timeout to configure an IO timeout","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-20T22:54:04Z","receivedAt":"2009-06-20T22:54:04Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> fredag 19 juni 2009 23:27:51 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> > An IO timeout can be useful if the remote peer stops responding,\n> > and the user wants the application to abort rather than block\n> > indefinitely waiting for more input.\n> > \n> > This is a JGit specific extension to the standard remote format.\n> \n> Can we we assume C Git won't implement the same thing with a different \n> parameter name, or worse, the same name, but a different unit?\n\nNo, we can't assume anything.\n\nI probably should add this to C Git too.  I think its the logical\nname and units, e.g `git daemon --timeout=` already exists and\ntakes seconds as the unit.\n\nI wrote this series because I have a case where the remote server\nis sometimes not sending packets out... and the client just blocks.\ngit fetch has the same issue.  Doing git fetch or jgit fetch from\na cron against this server causes the fetch processes to just pile\nup indefiniately.  :-(\n\n-- \nShawn.\n"},{"id":"116787","messageId":"200906222309.07435.robin.rosenberg@dewire.com","threadId":"19868","inReplyTo":"1245446875-31102-2-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 1/6] Create input and output streams that have timeouts","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2009-06-22T21:09:07Z","receivedAt":"2009-06-22T21:09:07Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"fredag 19 juni 2009 23:27:50 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> We can use these streams to wrap around a PipedInputStream, such\n> as the one given to us by JSch, to implement a timeout so we can\n> abort a transport operation if the remote peer doesn't communicate\n> with us in a timely fashion.\n> \n> Only the input and output streams and tests are defined, later\n> I will make use of them in the transport layer where we can't\n> rely upon raw socket timeouts to abort a long running IO call.\n> \n> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n> ---\n>  .../jgit/util/io/TimeoutInputStreamTest.java       |  187 +++++++++++++\n>  .../jgit/util/io/TimeoutOutputStreamTest.java      |  286 ++++++++++++++++++++\n>  .../org/spearce/jgit/util/io/InterruptTimer.java   |  216 +++++++++++++++\n>  .../spearce/jgit/util/io/TimeoutInputStream.java   |  133 +++++++++\n>  .../spearce/jgit/util/io/TimeoutOutputStream.java  |  146 ++++++++++\n>  5 files changed, 968 insertions(+), 0 deletions(-)\n>  create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n>  create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n>  create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n>  create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n>  create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n> \n> diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n> new file mode 100644\n> index 0000000..25eff9a\n> --- /dev/null\n> +++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n> @@ -0,0 +1,187 @@\n> +/*\n> + * Copyright (C) 2009, Google Inc.\n> + *\n> + * All rights reserved.\n> + *\n> + * Redistribution and use in source and binary forms, with or\n> + * without modification, are permitted provided that the following\n> + * conditions are met:\n> + *\n> + * - Redistributions of source code must retain the above copyright\n> + *   notice, this list of conditions and the following disclaimer.\n> + *\n> + * - Redistributions in binary form must reproduce the above\n> + *   copyright notice, this list of conditions and the following\n> + *   disclaimer in the documentation and/or other materials provided\n> + *   with the distribution.\n> + *\n> + * - Neither the name of the Git Development Community nor the\n> + *   names of its contributors may be used to endorse or promote\n> + *   products derived from this software without specific prior\n> + *   written permission.\n> + *\n> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n> + * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n> + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n> + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n> + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n> + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n> + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n> + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n> + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n> + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n> + */\n> +\n> +package org.spearce.jgit.util.io;\n> +\n> +import java.io.IOException;\n> +import java.io.InterruptedIOException;\n> +import java.io.PipedInputStream;\n> +import java.io.PipedOutputStream;\n> +import java.util.Arrays;\n> +import java.util.List;\n> +\n> +import org.spearce.jgit.util.NB;\n> +import org.spearce.jgit.util.io.InterruptTimer;\n> +import org.spearce.jgit.util.io.TimeoutInputStream;\n> +\n> +import junit.framework.TestCase;\n> +\n> +public class TimeoutInputStreamTest extends TestCase {\n> +\tprivate static final int timeout = 250;\n> +\n> +\tprivate PipedOutputStream out;\n> +\n> +\tprivate PipedInputStream in;\n> +\n> +\tprivate InterruptTimer timer;\n> +\n> +\tprivate TimeoutInputStream is;\n> +\n> +\tprivate long start;\n> +\n> +\tprotected void setUp() throws Exception {\n> +\t\tsuper.setUp();\n> +\t\tout = new PipedOutputStream();\n> +\t\tin = new PipedInputStream(out);\n> +\t\ttimer = new InterruptTimer();\n> +\t\tis = new TimeoutInputStream(in, timer);\n> +\t\tis.setTimeout(timeout);\n> +\t}\n> +\n> +\tprotected void tearDown() throws Exception {\n> +\t\tclose();\n> +\t\tsuper.tearDown();\n> +\t}\n> +\n> +\tpublic void testTimeout_readByte_Success1() throws IOException {\n> +\t\tout.write('a');\n> +\t\tassertEquals('a', is.read());\n> +\t\tclose();\nUnnecessary close(). It is already in tearDown. Same goes for all other\ntests in the suite.\n\n> +\t}\n> +\n> +\tpublic void testTimeout_readByte_Success2() throws IOException {\n> +\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n> +\t\tout.write(exp);\n> +\t\tassertEquals(exp[0], is.read());\n> +\t\tassertEquals(exp[1], is.read());\n> +\t\tassertEquals(exp[2], is.read());\n> +\t\tclose();\n> +\t}\n\nPerhaps a normal end of stream test too, out.close and verify that read\nreturns -1.\n\n> +\tpublic void testTimeout_readByte_Timeout() throws IOException {\n> +\t\tbeginRead();\n> +\t\ttry {\n> +\t\t\tis.read();\n> +\t\t\tfail(\"incorrectly read a byte\");\n> +\t\t} catch (InterruptedIOException e) {\n> +\t\t\t// expected\n> +\t\t}\n> +\t\tassertTimeout();\n> +\t\tclose();\n> +\t}\n> +\n> +\tpublic void testTimeout_readBuffer_Success1() throws IOException {\n> +\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n> +\t\tfinal byte[] act = new byte[exp.length];\n> +\t\tout.write(exp);\n> +\t\tNB.readFully(is, act, 0, act.length);\n> +\t\tassertTrue(Arrays.equals(exp, act));\n> +\t\tclose();\n> +\t}\n> +\n> +\tpublic void testTimeout_readBuffer_Success2() throws IOException {\n> +\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n> +\t\tfinal byte[] act = new byte[exp.length];\n> +\t\tout.write(exp);\n> +\t\tNB.readFully(is, act, 0, 1);\n> +\t\tNB.readFully(is, act, 1, 1);\n> +\t\tNB.readFully(is, act, 2, 1);\n> +\t\tassertTrue(Arrays.equals(exp, act));\n> +\t\tclose();\n> +\t}\n> +\n> +\tpublic void testTimeout_readBuffer_Timeout() throws IOException {\n> +\t\tbeginRead();\n> +\t\ttry {\n> +\t\t\tis.read(new byte[512]);\n> +\t\t\tfail(\"incorrectly read bytes\");\n> +\t\t} catch (InterruptedIOException e) {\n> +\t\t\t// expected\n> +\t\t}\n> +\t\tassertTimeout();\n> +\t\tclose();\n> +\t}\n\nThe success read calls NB.readFully, shouldn't the timeout ones use the\nsame API?\n\n> +\tpublic void testTimeout_skip_Success() throws IOException {\n> +\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n> +\t\tout.write(exp);\n> +\t\tassertEquals(2, is.skip(2));\n> +\t\tassertEquals('c', is.read());\n> +\t\tclose();\n> +\t}\n> +\n> +\tpublic void testTimeout_skip_Timeout() throws IOException {\n> +\t\tbeginRead();\n> +\t\ttry {\n> +\t\t\tis.skip(1024);\n> +\t\t\tfail(\"incorrectly skipped bytes\");\n> +\t\t} catch (InterruptedIOException e) {\n> +\t\t\t// expected\n> +\t\t}\n> +\t\tassertTimeout();\n> +\t\tclose();\n> +\t}\n> +\n> +\tprivate void beginRead() {\n> +\t\tstart = now();\n> +\t}\n> +\n> +\tprivate void assertTimeout() {\n> +\t\tfinal long wait = now() - start;\n> +\t\tassertTrue(Math.abs(wait - timeout) < 50);\n\nMaybe a comment to the uninvited.\n\n> +\t}\n...\n> +public class TimeoutOutputStreamTest extends TestCase {\n> +\tprivate static final int timeout = 250;\n...\n> +\tprotected void tearDown() throws Exception {\n> +\t\tclose();\n> +\t\tsuper.tearDown();\n> +\t}\n> +\nSame comment as for the input test and close().\n\n\nAnd the rest is beautiful code.\n\n-- robin\n"},{"id":"116826","messageId":"20090623164157.GB11191@spearce.org","threadId":"19868","inReplyTo":"200906222309.07435.robin.rosenberg@dewire.com","subject":"[JGIT PATCH 1/6 v2] Create input and output streams that have timeouts","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-23T16:41:57Z","receivedAt":"2009-06-23T16:41:57Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"We can use these streams to wrap around a PipedInputStream, such\nas the one given to us by JSch, to implement a timeout so we can\nabort a transport operation if the remote peer doesn't communicate\nwith us in a timely fashion.\n\nOnly the input and output streams and tests are defined, later\nI will make use of them in the transport layer where we can't\nrely upon raw socket timeouts to abort a long running IO call.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n\n This version addresses all of your comments in the unit tests.\n\n .../jgit/util/io/TimeoutInputStreamTest.java       |  183 +++++++++++++\n .../jgit/util/io/TimeoutOutputStreamTest.java      |  279 ++++++++++++++++++++\n .../org/spearce/jgit/util/io/InterruptTimer.java   |  216 +++++++++++++++\n .../spearce/jgit/util/io/TimeoutInputStream.java   |  133 ++++++++++\n .../spearce/jgit/util/io/TimeoutOutputStream.java  |  146 ++++++++++\n 5 files changed, 957 insertions(+), 0 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\nnew file mode 100644\nindex 0000000..272838c\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutInputStreamTest.java\n@@ -0,0 +1,183 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.PipedInputStream;\n+import java.io.PipedOutputStream;\n+import java.util.Arrays;\n+import java.util.List;\n+\n+import org.spearce.jgit.util.NB;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutInputStream;\n+\n+import junit.framework.TestCase;\n+\n+public class TimeoutInputStreamTest extends TestCase {\n+\tprivate static final int timeout = 250;\n+\n+\tprivate PipedOutputStream out;\n+\n+\tprivate PipedInputStream in;\n+\n+\tprivate InterruptTimer timer;\n+\n+\tprivate TimeoutInputStream is;\n+\n+\tprivate long start;\n+\n+\tprotected void setUp() throws Exception {\n+\t\tsuper.setUp();\n+\t\tout = new PipedOutputStream();\n+\t\tin = new PipedInputStream(out);\n+\t\ttimer = new InterruptTimer();\n+\t\tis = new TimeoutInputStream(in, timer);\n+\t\tis.setTimeout(timeout);\n+\t}\n+\n+\tprotected void tearDown() throws Exception {\n+\t\ttimer.terminate();\n+\t\tfor (Thread t : active())\n+\t\t\tassertFalse(t instanceof InterruptTimer.AlarmThread);\n+\t\tsuper.tearDown();\n+\t}\n+\n+\tpublic void testTimeout_readByte_Success1() throws IOException {\n+\t\tout.write('a');\n+\t\tassertEquals('a', is.read());\n+\t}\n+\n+\tpublic void testTimeout_readByte_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tout.write(exp);\n+\t\tassertEquals(exp[0], is.read());\n+\t\tassertEquals(exp[1], is.read());\n+\t\tassertEquals(exp[2], is.read());\n+\t\tout.close();\n+\t\tassertEquals(-1, is.read());\n+\t}\n+\n+\tpublic void testTimeout_readByte_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tis.read();\n+\t\t\tfail(\"incorrectly read a byte\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Success1() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tout.write(exp);\n+\t\tNB.readFully(is, act, 0, act.length);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tout.write(exp);\n+\t\tNB.readFully(is, act, 0, 1);\n+\t\tNB.readFully(is, act, 1, 1);\n+\t\tNB.readFully(is, act, 2, 1);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t}\n+\n+\tpublic void testTimeout_readBuffer_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tNB.readFully(is, new byte[512], 0, 512);\n+\t\t\tfail(\"incorrectly read bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t}\n+\n+\tpublic void testTimeout_skip_Success() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tout.write(exp);\n+\t\tassertEquals(2, is.skip(2));\n+\t\tassertEquals('c', is.read());\n+\t}\n+\n+\tpublic void testTimeout_skip_Timeout() throws IOException {\n+\t\tbeginRead();\n+\t\ttry {\n+\t\t\tis.skip(1024);\n+\t\t\tfail(\"incorrectly skipped bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t}\n+\n+\tprivate void beginRead() {\n+\t\tstart = now();\n+\t}\n+\n+\tprivate void assertTimeout() {\n+\t\t// Our timeout was supposed to be ~250 ms. Since this is a timing\n+\t\t// test we can't assume we spent *exactly* the timeout period, as\n+\t\t// there may be other activity going on in the system. Instead we\n+\t\t// look for the delta between the start and end times to be within\n+\t\t// 50 ms of the expected timeout.\n+\t\t//\n+\t\tfinal long wait = now() - start;\n+\t\tassertTrue(Math.abs(wait - timeout) < 50);\n+\t}\n+\n+\tprivate static List<Thread> active() {\n+\t\tThread[] all = new Thread[16];\n+\t\tint n = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\twhile (n == all.length) {\n+\t\t\tall = new Thread[all.length * 2];\n+\t\t\tn = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\t}\n+\t\treturn Arrays.asList(all).subList(0, n);\n+\t}\n+\n+\tprivate static long now() {\n+\t\treturn System.currentTimeMillis();\n+\t}\n+}\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\nnew file mode 100644\nindex 0000000..07e5c38\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/util/io/TimeoutOutputStreamTest.java\n@@ -0,0 +1,279 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.OutputStream;\n+import java.io.PipedInputStream;\n+import java.io.PipedOutputStream;\n+import java.util.Arrays;\n+import java.util.List;\n+\n+import org.spearce.jgit.util.NB;\n+import org.spearce.jgit.util.io.InterruptTimer;\n+import org.spearce.jgit.util.io.TimeoutOutputStream;\n+\n+import junit.framework.TestCase;\n+\n+public class TimeoutOutputStreamTest extends TestCase {\n+\tprivate static final int timeout = 250;\n+\n+\tprivate PipedOutputStream out;\n+\n+\tprivate FullPipeInputStream in;\n+\n+\tprivate InterruptTimer timer;\n+\n+\tprivate TimeoutOutputStream os;\n+\n+\tprivate long start;\n+\n+\tprotected void setUp() throws Exception {\n+\t\tsuper.setUp();\n+\t\tout = new PipedOutputStream();\n+\t\tin = new FullPipeInputStream(out);\n+\t\ttimer = new InterruptTimer();\n+\t\tos = new TimeoutOutputStream(out, timer);\n+\t\tos.setTimeout(timeout);\n+\t}\n+\n+\tprotected void tearDown() throws Exception {\n+\t\ttimer.terminate();\n+\t\tfor (Thread t : active())\n+\t\t\tassertFalse(t instanceof InterruptTimer.AlarmThread);\n+\t\tsuper.tearDown();\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Success1() throws IOException {\n+\t\tin.free(1);\n+\t\tos.write('a');\n+\t\tin.want(1);\n+\t\tassertEquals('a', in.read());\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Success2() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tin.free(exp.length);\n+\t\tos.write(exp[0]);\n+\t\tos.write(exp[1]);\n+\t\tos.write(exp[2]);\n+\t\tin.want(exp.length);\n+\t\tin.read(act);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t}\n+\n+\tpublic void testTimeout_writeByte_Timeout() throws IOException {\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.write('\\n');\n+\t\t\tfail(\"incorrectly write a byte\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t}\n+\n+\tpublic void testTimeout_writeBuffer_Success1() throws IOException {\n+\t\tfinal byte[] exp = new byte[] { 'a', 'b', 'c' };\n+\t\tfinal byte[] act = new byte[exp.length];\n+\t\tin.free(exp.length);\n+\t\tos.write(exp);\n+\t\tin.want(exp.length);\n+\t\tin.read(act);\n+\t\tassertTrue(Arrays.equals(exp, act));\n+\t}\n+\n+\tpublic void testTimeout_writeBuffer_Timeout() throws IOException {\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.write(new byte[512]);\n+\t\t\tfail(\"incorrectly wrote bytes\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t}\n+\n+\tpublic void testTimeout_flush_Success() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void flush() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\t\tos.flush();\n+\t\tassertTrue(called[0]);\n+\t}\n+\n+\tpublic void testTimeout_flush_Timeout() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void flush() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tThread.sleep(1000);\n+\t\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t\tthrow new InterruptedIOException();\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.flush();\n+\t\t\tfail(\"incorrectly flushed\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tassertTrue(called[0]);\n+\t}\n+\n+\tpublic void testTimeout_close_Success() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void close() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\t\tos.close();\n+\t\tassertTrue(called[0]);\n+\t}\n+\n+\tpublic void testTimeout_close_Timeout() throws IOException {\n+\t\tfinal boolean[] called = new boolean[1];\n+\t\tos = new TimeoutOutputStream(new OutputStream() {\n+\t\t\t@Override\n+\t\t\tpublic void write(int b) throws IOException {\n+\t\t\t\tfail(\"should not have written\");\n+\t\t\t}\n+\n+\t\t\t@Override\n+\t\t\tpublic void close() throws IOException {\n+\t\t\t\tcalled[0] = true;\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\ttry {\n+\t\t\t\t\t\tThread.sleep(1000);\n+\t\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t\tthrow new InterruptedIOException();\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}, timer);\n+\t\tos.setTimeout(timeout);\n+\n+\t\tbeginWrite();\n+\t\ttry {\n+\t\t\tos.close();\n+\t\t\tfail(\"incorrectly closed\");\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\t// expected\n+\t\t}\n+\t\tassertTimeout();\n+\t\tassertTrue(called[0]);\n+\t}\n+\n+\tprivate void beginWrite() {\n+\t\tstart = now();\n+\t}\n+\n+\tprivate void assertTimeout() {\n+\t\t// Our timeout was supposed to be ~250 ms. Since this is a timing\n+\t\t// test we can't assume we spent *exactly* the timeout period, as\n+\t\t// there may be other activity going on in the system. Instead we\n+\t\t// look for the delta between the start and end times to be within\n+\t\t// 50 ms of the expected timeout.\n+\t\t//\n+\t\tfinal long wait = now() - start;\n+\t\tassertTrue(Math.abs(wait - timeout) < 50);\n+\t}\n+\n+\tprivate static List<Thread> active() {\n+\t\tThread[] all = new Thread[16];\n+\t\tint n = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\twhile (n == all.length) {\n+\t\t\tall = new Thread[all.length * 2];\n+\t\t\tn = Thread.currentThread().getThreadGroup().enumerate(all);\n+\t\t}\n+\t\treturn Arrays.asList(all).subList(0, n);\n+\t}\n+\n+\tprivate static long now() {\n+\t\treturn System.currentTimeMillis();\n+\t}\n+\n+\tprivate final class FullPipeInputStream extends PipedInputStream {\n+\t\tFullPipeInputStream(PipedOutputStream src) throws IOException {\n+\t\t\tsuper(src);\n+\t\t\tsrc.write(new byte[PIPE_SIZE]);\n+\t\t}\n+\n+\t\tvoid want(int cnt) throws IOException {\n+\t\t\tNB.skipFully(this, PIPE_SIZE - cnt);\n+\t\t}\n+\n+\t\tvoid free(int cnt) throws IOException {\n+\t\t\tNB.skipFully(this, cnt);\n+\t\t}\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\nnew file mode 100644\nindex 0000000..8f625e0\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/InterruptTimer.java\n@@ -0,0 +1,216 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+/**\n+ * Triggers an interrupt on the calling thread if it doesn't complete a block.\n+ * <p>\n+ * Classes can use this to trip an alarm interrupting the calling thread if it\n+ * doesn't complete a block within the specified timeout. Typical calling\n+ * pattern is:\n+ *\n+ * <pre>\n+ * private InterruptTimer myTimer = ...;\n+ * void foo() {\n+ *   try {\n+ *     myTimer.begin(timeout);\n+ *     // work\n+ *   } finally {\n+ *     myTimer.end();\n+ *   }\n+ * }\n+ * </pre>\n+ * <p>\n+ * An InterruptTimer is not recursive. To implement recursive timers,\n+ * independent InterruptTimer instances are required. A single InterruptTimer\n+ * may be shared between objects which won't recursively call each other.\n+ * <p>\n+ * Each InterruptTimer spawns one background thread to sleep the specified time\n+ * and interrupt the thread which called {@link #begin(int)}. It is up to the\n+ * caller to ensure that the operations within the work block between the\n+ * matched begin and end calls tests the interrupt flag (most IO operations do).\n+ * <p>\n+ * To terminate the background thread, use {@link #terminate()}. If the\n+ * application fails to terminate the thread, it will (eventually) terminate\n+ * itself when the InterruptTimer instance is garbage collected.\n+ *\n+ * @see TimeoutInputStream\n+ */\n+public final class InterruptTimer {\n+\tprivate final AlarmState state;\n+\n+\tprivate final AlarmThread thread;\n+\n+\tfinal AutoKiller autoKiller;\n+\n+\t/** Create a new timer with a default thread name. */\n+\tpublic InterruptTimer() {\n+\t\tthis(\"JGit-InterruptTimer\");\n+\t}\n+\n+\t/**\n+\t * Create a new timer to signal on interrupt on the caller.\n+\t * <p>\n+\t * The timer thread is created in the calling thread's ThreadGroup.\n+\t *\n+\t * @param threadName\n+\t *            name of the timer thread.\n+\t */\n+\tpublic InterruptTimer(final String threadName) {\n+\t\tstate = new AlarmState();\n+\t\tautoKiller = new AutoKiller(state);\n+\t\tthread = new AlarmThread(threadName, state);\n+\t\tthread.start();\n+\t}\n+\n+\t/**\n+\t * Arm the interrupt timer before entering a blocking operation.\n+\t *\n+\t * @param timeout\n+\t *            number of milliseconds before the interrupt should trigger.\n+\t *            Must be > 0.\n+\t */\n+\tpublic void begin(final int timeout) {\n+\t\tif (timeout <= 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + timeout);\n+\t\tThread.interrupted();\n+\t\tstate.begin(timeout);\n+\t}\n+\n+\t/** Disable the interrupt timer, as the operation is complete. */\n+\tpublic void end() {\n+\t\tstate.end();\n+\t}\n+\n+\t/** Shutdown the timer thread, and wait for it to terminate. */\n+\tpublic void terminate() {\n+\t\tstate.terminate();\n+\t\ttry {\n+\t\t\tthread.join();\n+\t\t} catch (InterruptedException e) {\n+\t\t\t//\n+\t\t}\n+\t}\n+\n+\tstatic final class AlarmThread extends Thread {\n+\t\tAlarmThread(final String name, final AlarmState q) {\n+\t\t\tsuper(q);\n+\t\t\tsetName(name);\n+\t\t\tsetDaemon(true);\n+\t\t}\n+\t}\n+\n+\t// The trick here is, the AlarmThread does not have a reference to the\n+\t// AutoKiller instance, only the InterruptTimer itself does. Thus when\n+\t// the InterruptTimer is GC'd, the AutoKiller is also unreachable and\n+\t// can be GC'd. When it gets finalized, it tells the AlarmThread to\n+\t// terminate, triggering the thread to exit gracefully.\n+\t//\n+\tprivate static final class AutoKiller {\n+\t\tprivate final AlarmState state;\n+\n+\t\tAutoKiller(final AlarmState s) {\n+\t\t\tstate = s;\n+\t\t}\n+\n+\t\t@Override\n+\t\tprotected void finalize() throws Throwable {\n+\t\t\tstate.terminate();\n+\t\t}\n+\t}\n+\n+\tstatic final class AlarmState implements Runnable {\n+\t\tprivate Thread callingThread;\n+\n+\t\tprivate long deadline;\n+\n+\t\tprivate boolean terminated;\n+\n+\t\tAlarmState() {\n+\t\t\tcallingThread = Thread.currentThread();\n+\t\t}\n+\n+\t\tpublic synchronized void run() {\n+\t\t\twhile (!terminated && callingThread.isAlive()) {\n+\t\t\t\ttry {\n+\t\t\t\t\tif (0 < deadline) {\n+\t\t\t\t\t\tfinal long delay = deadline - now();\n+\t\t\t\t\t\tif (delay <= 0) {\n+\t\t\t\t\t\t\tdeadline = 0;\n+\t\t\t\t\t\t\tcallingThread.interrupt();\n+\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\twait(delay);\n+\t\t\t\t\t\t}\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\twait(1000);\n+\t\t\t\t\t}\n+\t\t\t\t} catch (InterruptedException e) {\n+\t\t\t\t\t// Treat an interrupt as notice to examine state.\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tsynchronized void begin(final int timeout) {\n+\t\t\tif (terminated)\n+\t\t\t\tthrow new IllegalStateException(\"Timer already terminated\");\n+\t\t\tcallingThread = Thread.currentThread();\n+\t\t\tdeadline = now() + timeout;\n+\t\t\tnotifyAll();\n+\t\t}\n+\n+\t\tsynchronized void end() {\n+\t\t\tif (0 == deadline)\n+\t\t\t\tThread.interrupted();\n+\t\t\telse\n+\t\t\t\tdeadline = 0;\n+\t\t\tnotifyAll();\n+\t\t}\n+\n+\t\tsynchronized void terminate() {\n+\t\t\tif (!terminated) {\n+\t\t\t\tdeadline = 0;\n+\t\t\t\tterminated = true;\n+\t\t\t\tnotifyAll();\n+\t\t\t}\n+\t\t}\n+\n+\t\tprivate static long now() {\n+\t\t\treturn System.currentTimeMillis();\n+\t\t}\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\nnew file mode 100644\nindex 0000000..3a321aa\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutInputStream.java\n@@ -0,0 +1,133 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.FilterInputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.InterruptedIOException;\n+\n+/** InputStream with a configurable timeout. */\n+public class TimeoutInputStream extends FilterInputStream {\n+\tprivate final InterruptTimer myTimer;\n+\n+\tprivate int timeout;\n+\n+\t/**\n+\t * Wrap an input stream with a timeout on all read operations.\n+\t *\n+\t * @param src\n+\t *            base input stream (to read from). The stream must be\n+\t *            interruptible (most socket streams are).\n+\t * @param timer\n+\t *            timer to manage the timeouts during reads.\n+\t */\n+\tpublic TimeoutInputStream(final InputStream src,\n+\t\t\tfinal InterruptTimer timer) {\n+\t\tsuper(src);\n+\t\tmyTimer = timer;\n+\t}\n+\n+\t/** @return number of milliseconds before aborting a read. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * @param millis\n+\t *            number of milliseconds before aborting a read. Must be > 0.\n+\t */\n+\tpublic void setTimeout(final int millis) {\n+\t\tif (millis < 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + millis);\n+\t\ttimeout = millis;\n+\t}\n+\n+\t@Override\n+\tpublic int read() throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.read();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic int read(byte[] buf) throws IOException {\n+\t\treturn read(buf, 0, buf.length);\n+\t}\n+\n+\t@Override\n+\tpublic int read(byte[] buf, int off, int cnt) throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.read(buf, off, cnt);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic long skip(long cnt) throws IOException {\n+\t\ttry {\n+\t\t\tbeginRead();\n+\t\t\treturn super.skip(cnt);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow readTimedOut();\n+\t\t} finally {\n+\t\t\tendRead();\n+\t\t}\n+\t}\n+\n+\tprivate void beginRead() {\n+\t\tmyTimer.begin(timeout);\n+\t}\n+\n+\tprivate void endRead() {\n+\t\tmyTimer.end();\n+\t}\n+\n+\tprivate static InterruptedIOException readTimedOut() {\n+\t\treturn new InterruptedIOException(\"Read timed out\");\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\nnew file mode 100644\nindex 0000000..014cd92\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/io/TimeoutOutputStream.java\n@@ -0,0 +1,146 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+package org.spearce.jgit.util.io;\n+\n+import java.io.IOException;\n+import java.io.InterruptedIOException;\n+import java.io.OutputStream;\n+\n+/** OutputStream with a configurable timeout. */\n+public class TimeoutOutputStream extends OutputStream {\n+\tprivate final OutputStream dst;\n+\n+\tprivate final InterruptTimer myTimer;\n+\n+\tprivate int timeout;\n+\n+\t/**\n+\t * Wrap an output stream with a timeout on all write operations.\n+\t *\n+\t * @param destination\n+\t *            base input stream (to write to). The stream must be\n+\t *            interruptible (most socket streams are).\n+\t * @param timer\n+\t *            timer to manage the timeouts during writes.\n+\t */\n+\tpublic TimeoutOutputStream(final OutputStream destination,\n+\t\t\tfinal InterruptTimer timer) {\n+\t\tdst = destination;\n+\t\tmyTimer = timer;\n+\t}\n+\n+\t/** @return number of milliseconds before aborting a write. */\n+\tpublic int getTimeout() {\n+\t\treturn timeout;\n+\t}\n+\n+\t/**\n+\t * @param millis\n+\t *            number of milliseconds before aborting a write. Must be > 0.\n+\t */\n+\tpublic void setTimeout(final int millis) {\n+\t\tif (millis < 0)\n+\t\t\tthrow new IllegalArgumentException(\"Invalid timeout: \" + millis);\n+\t\ttimeout = millis;\n+\t}\n+\n+\t@Override\n+\tpublic void write(int b) throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.write(b);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void write(byte[] buf) throws IOException {\n+\t\twrite(buf, 0, buf.length);\n+\t}\n+\n+\t@Override\n+\tpublic void write(byte[] buf, int off, int len) throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.write(buf, off, len);\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void flush() throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.flush();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\t@Override\n+\tpublic void close() throws IOException {\n+\t\ttry {\n+\t\t\tbeginWrite();\n+\t\t\tdst.close();\n+\t\t} catch (InterruptedIOException e) {\n+\t\t\tthrow writeTimedOut();\n+\t\t} finally {\n+\t\t\tendWrite();\n+\t\t}\n+\t}\n+\n+\tprivate void beginWrite() {\n+\t\tmyTimer.begin(timeout);\n+\t}\n+\n+\tprivate void endWrite() {\n+\t\tmyTimer.end();\n+\t}\n+\n+\tprivate static InterruptedIOException writeTimedOut() {\n+\t\treturn new InterruptedIOException(\"Write timed out\");\n+\t}\n+}\n-- \n1.6.3.2.416.g04d0\n"}]}