From d886f6c03cca051b45fd77cc77d0cc870aed1aed Mon Sep 17 00:00:00 2001
From: build <build@obs.com>
Date: Wed, 4 Sep 2019 05:21:06 -0400
Subject: [PATCH] containerd: add timeout for I/O waitgroups

reason: This and a combination of a couple Docker changes are needed to fully
resolve the issue on the Docker side.  However, this ensures that after
processes exit, we still leave some time for the I/O to fully flush
before closing.  Without this timeout, the delete methods would block
forever.

Cherry-pick from upstream 245052243d
Reference from https://github.com/containerd/containerd/pull/3361

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
---
 runtime/v1/linux/proc/exec.go  |  2 +-
 runtime/v1/linux/proc/init.go  |  2 +-
 runtime/v1/linux/proc/utils.go | 20 ++++++++++++++++++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/runtime/v1/linux/proc/exec.go b/runtime/v1/linux/proc/exec.go
index 715a977..08c581f 100644
--- a/runtime/v1/linux/proc/exec.go
+++ b/runtime/v1/linux/proc/exec.go
@@ -94,7 +94,7 @@ func (e *execProcess) setExited(status int) {
 }
 
 func (e *execProcess) delete(ctx context.Context) error {
-	e.wg.Wait()
+	waitTimeout(ctx, &e.wg, 2*time.Second)
 	if e.io != nil {
 		for _, c := range e.closers {
 			c.Close()
diff --git a/runtime/v1/linux/proc/init.go b/runtime/v1/linux/proc/init.go
index 44d3f58..49fa8ec 100644
--- a/runtime/v1/linux/proc/init.go
+++ b/runtime/v1/linux/proc/init.go
@@ -263,7 +263,7 @@ func (p *Init) setExited(status int) {
 }
 
 func (p *Init) delete(context context.Context) error {
-	p.wg.Wait()
+	waitTimeout(context, &p.wg, 2*time.Second)
 	err := p.runtime.Delete(context, p.id, nil)
 	// ignore errors if a runtime has already deleted the process
 	// but we still hold metadata and pipes
diff --git a/runtime/v1/linux/proc/utils.go b/runtime/v1/linux/proc/utils.go
index ab9f5fa..d6f047c 100644
--- a/runtime/v1/linux/proc/utils.go
+++ b/runtime/v1/linux/proc/utils.go
@@ -19,10 +19,12 @@
 package proc
 
 import (
+	"context"
 	"encoding/json"
 	"io"
 	"os"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/containerd/containerd/errdefs"
@@ -103,3 +105,21 @@ func checkKillError(err error) error {
 func hasNoIO(r *CreateConfig) bool {
 	return r.Stdin == "" && r.Stdout == "" && r.Stderr == ""
 }
+
+// waitTimeout handles waiting on a waitgroup with a specified timeout.
+// this is commonly used for waiting on IO to finish after a process has exited
+func waitTimeout(ctx context.Context, wg *sync.WaitGroup, timeout time.Duration) error {
+	ctx, cancel := context.WithTimeout(ctx, timeout)
+	defer cancel()
+	done := make(chan struct{}, 1)
+	go func() {
+		wg.Wait()
+		close(done)
+	}()
+	select {
+	case <-done:
+		return nil
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+}
-- 
2.20.1

