Browse code

Fix panic on network timeout during push

`Upload` already closes the reader returned by `compress` and the
progressreader passed into it, before returning. But even so, the
io.Copy inside compress' goroutine needs to attempt a read from the
progressreader to notice that it's closed, and this read has a side
effect of outputting a progress message. If this happens after `Upload`
returns, it can result in a write to a closed channel. Change `compress`
to return a channel that allows the caller to wait for its goroutine to
finish before freeing any resources connected to the reader that was
passed to it.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2016/01/30 07:34:50
Showing 2 changed files
... ...
@@ -170,7 +170,14 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
170 170
 // argument so that it can be used with httpBlobWriter's ReadFrom method.
171 171
 // Using httpBlobWriter's Write method would send a PATCH request for every
172 172
 // Write call.
173
-func compress(in io.Reader) io.ReadCloser {
173
+//
174
+// The second return value is a channel that gets closed when the goroutine
175
+// is finished. This allows the caller to make sure the goroutine finishes
176
+// before it releases any resources connected with the reader that was
177
+// passed in.
178
+func compress(in io.Reader) (io.ReadCloser, chan struct{}) {
179
+	compressionDone := make(chan struct{})
180
+
174 181
 	pipeReader, pipeWriter := io.Pipe()
175 182
 	// Use a bufio.Writer to avoid excessive chunking in HTTP request.
176 183
 	bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize)
... ...
@@ -189,7 +196,8 @@ func compress(in io.Reader) io.ReadCloser {
189 189
 		} else {
190 190
 			pipeWriter.Close()
191 191
 		}
192
+		close(compressionDone)
192 193
 	}()
193 194
 
194
-	return pipeReader
195
+	return pipeReader, compressionDone
195 196
 }
... ...
@@ -345,8 +345,11 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
345 345
 	size, _ := pd.layer.DiffSize()
346 346
 
347 347
 	reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), progressOutput, size, pd.ID(), "Pushing")
348
-	defer reader.Close()
349
-	compressedReader := compress(reader)
348
+	compressedReader, compressionDone := compress(reader)
349
+	defer func() {
350
+		reader.Close()
351
+		<-compressionDone
352
+	}()
350 353
 
351 354
 	digester := digest.Canonical.New()
352 355
 	tee := io.TeeReader(compressedReader, digester.Hash())