Set idle timeouts for HTTP reads and writes in communications with the registry
Otherwise, some operations can get stuck indefinitely when the remote
side is unresponsive.
Fixes #12823
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
diff --git a/distribution/registry.go b/distribution/registry.go
index 1c2b4f3..fd3d552 100644
--- a/distribution/registry.go
+++ b/distribution/registry.go
@@ -44,6 +44,30 @@
return dcs.auth.Username, dcs.auth.Password
}
+// conn wraps a net.Conn, and sets a deadline for every read
+// and write operation.
+type conn struct {
+ net.Conn
+ readTimeout time.Duration
+ writeTimeout time.Duration
+}
+
+func (c *conn) Read(b []byte) (int, error) {
+ err := c.Conn.SetReadDeadline(time.Now().Add(c.readTimeout))
+ if err != nil {
+ return 0, err
+ }
+ return c.Conn.Read(b)
+}
+
+func (c *conn) Write(b []byte) (int, error) {
+ err := c.Conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ if err != nil {
+ return 0, err
+ }
+ return c.Conn.Write(b)
+}
+
// NewV2Repository returns a repository (v2 only). It creates a HTTP transport
// providing timeout settings and authentication support, and also verifies the
// remote API version.
@@ -57,11 +81,22 @@
// TODO(dmcgowan): Call close idle connections when complete, use keep alive
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
- Dial: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- DualStack: true,
- }).Dial,
+ Dial: func(network, address string) (net.Conn, error) {
+ dialer := &net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }
+ netConn, err := dialer.Dial(network, address)
+ if err != nil {
+ return netConn, err
+ }
+ return &conn{
+ Conn: netConn,
+ readTimeout: time.Minute,
+ writeTimeout: time.Minute,
+ }, nil
+ },
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: endpoint.TLSConfig,
// TODO(dmcgowan): Call close idle connections when complete and use keep alive