Add an idle timeout to runner.go.

If a Read or Write blocks for too long, time out the operation. Otherwise, some
kinds of test failures result in hangs, which prevent the test harness from
progressing. (Notably, OpenSSL currently has a lot of those failure modes and
upstream expressed interest in being able to run the tests to completion.)

Go's APIs want you to send an absolute timeout, to avoid problems when a Read
is split into lots of little Reads. But we actively want the timer to reset in
that case, so this needs a trivial adapter.

The default timeout is set at 15 seconds for now. If this becomes a problem, we
can extend it or build a more robust deadlock detector given an out-of-band
channel (shim tells runner when it's waiting on data, abort if we're also
waiting on data at the same time). But I don't think we'll need that
complexity. 15 seconds appears fine for both valgrind and running tests on a
Nexus 4.


Change-Id: I6463fd36058427d883b526044da1bbefba851785
Reviewed-by: Steven Valdez <>
Reviewed-by: David Benjamin <>
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 061aee3..fe53b5f 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -39,6 +39,7 @@
 	resourceDir     = flag.String("resource-dir", ".", "The directory in which to find certificate and key files.")
 	fuzzer          = flag.Bool("fuzzer", false, "If true, tests against a BoringSSL built in fuzzer mode.")
 	transcriptDir   = flag.String("transcript-dir", "", "The directory in which to write transcripts.")
+	timeout         = flag.Int("timeout", 15, "The number of seconds to wait for a read or write to bssl_shim.")
 const (
@@ -279,8 +280,28 @@
+// A timeoutConn implements an idle timeout on each Read and Write operation.
+type timeoutConn struct {
+	net.Conn
+	timeout time.Duration
+func (t *timeoutConn) Read(b []byte) (int, error) {
+	if err := t.SetReadDeadline(time.Now().Add(t.timeout)); err != nil {
+		return 0, err
+	}
+	return t.Conn.Read(b)
+func (t *timeoutConn) Write(b []byte) (int, error) {
+	if err := t.SetWriteDeadline(time.Now().Add(t.timeout)); err != nil {
+		return 0, err
+	}
+	return t.Conn.Write(b)
 func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool) error {
-	var connDamage *damageAdaptor
+	conn = &timeoutConn{conn, time.Duration(*timeout) * time.Second}
 	if test.protocol == dtls {
 		config.Bugs.PacketAdaptor = newPacketAdaptor(conn)
@@ -317,6 +338,7 @@
 		conn = newReplayAdaptor(conn)
+	var connDamage *damageAdaptor
 	if test.damageFirstWrite {
 		connDamage = newDamageAdaptor(conn)
 		conn = connDamage