Browse code

Move types from progressreader and broadcastwriter to broadcaster

progressreader.Broadcaster becomes broadcaster.Buffered and
broadcastwriter.Writer becomes broadcaster.Unbuffered.

The package broadcastwriter is thus renamed to broadcaster.

Signed-off-by: Tibor Vass <tibor@docker.com>

Tibor Vass authored on 2015/10/07 02:24:49
Showing 12 changed files
... ...
@@ -22,7 +22,7 @@ import (
22 22
 	derr "github.com/docker/docker/errors"
23 23
 	"github.com/docker/docker/image"
24 24
 	"github.com/docker/docker/pkg/archive"
25
-	"github.com/docker/docker/pkg/broadcastwriter"
25
+	"github.com/docker/docker/pkg/broadcaster"
26 26
 	"github.com/docker/docker/pkg/fileutils"
27 27
 	"github.com/docker/docker/pkg/ioutils"
28 28
 	"github.com/docker/docker/pkg/mount"
... ...
@@ -41,8 +41,8 @@ var (
41 41
 )
42 42
 
43 43
 type streamConfig struct {
44
-	stdout    *broadcastwriter.BroadcastWriter
45
-	stderr    *broadcastwriter.BroadcastWriter
44
+	stdout    *broadcaster.Unbuffered
45
+	stderr    *broadcaster.Unbuffered
46 46
 	stdin     io.ReadCloser
47 47
 	stdinPipe io.WriteCloser
48 48
 }
... ...
@@ -318,13 +318,13 @@ func (streamConfig *streamConfig) StdinPipe() io.WriteCloser {
318 318
 
319 319
 func (streamConfig *streamConfig) StdoutPipe() io.ReadCloser {
320 320
 	reader, writer := io.Pipe()
321
-	streamConfig.stdout.AddWriter(writer)
321
+	streamConfig.stdout.Add(writer)
322 322
 	return ioutils.NewBufReader(reader)
323 323
 }
324 324
 
325 325
 func (streamConfig *streamConfig) StderrPipe() io.ReadCloser {
326 326
 	reader, writer := io.Pipe()
327
-	streamConfig.stderr.AddWriter(writer)
327
+	streamConfig.stderr.Add(writer)
328 328
 	return ioutils.NewBufReader(reader)
329 329
 }
330 330
 
... ...
@@ -32,7 +32,7 @@ import (
32 32
 	"github.com/docker/docker/graph"
33 33
 	"github.com/docker/docker/image"
34 34
 	"github.com/docker/docker/pkg/archive"
35
-	"github.com/docker/docker/pkg/broadcastwriter"
35
+	"github.com/docker/docker/pkg/broadcaster"
36 36
 	"github.com/docker/docker/pkg/discovery"
37 37
 	"github.com/docker/docker/pkg/fileutils"
38 38
 	"github.com/docker/docker/pkg/graphdb"
... ...
@@ -194,8 +194,8 @@ func (daemon *Daemon) Register(container *Container) error {
194 194
 	container.daemon = daemon
195 195
 
196 196
 	// Attach to stdout and stderr
197
-	container.stderr = broadcastwriter.New()
198
-	container.stdout = broadcastwriter.New()
197
+	container.stderr = new(broadcaster.Unbuffered)
198
+	container.stdout = new(broadcaster.Unbuffered)
199 199
 	// Attach to stdin
200 200
 	if container.Config.OpenStdin {
201 201
 		container.stdin, container.stdinPipe = io.Pipe()
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"github.com/Sirupsen/logrus"
11 11
 	"github.com/docker/docker/daemon/execdriver"
12 12
 	derr "github.com/docker/docker/errors"
13
-	"github.com/docker/docker/pkg/broadcastwriter"
13
+	"github.com/docker/docker/pkg/broadcaster"
14 14
 	"github.com/docker/docker/pkg/ioutils"
15 15
 	"github.com/docker/docker/pkg/pools"
16 16
 	"github.com/docker/docker/pkg/stringid"
... ...
@@ -233,8 +233,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
233 233
 		cStderr = stderr
234 234
 	}
235 235
 
236
-	ec.streamConfig.stderr = broadcastwriter.New()
237
-	ec.streamConfig.stdout = broadcastwriter.New()
236
+	ec.streamConfig.stderr = new(broadcaster.Unbuffered)
237
+	ec.streamConfig.stdout = new(broadcaster.Unbuffered)
238 238
 	// Attach to stdin
239 239
 	if ec.OpenStdin {
240 240
 		ec.streamConfig.stdin, ec.streamConfig.stdinPipe = io.Pipe()
... ...
@@ -3,7 +3,7 @@ package graph
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"github.com/docker/docker/pkg/progressreader"
6
+	"github.com/docker/docker/pkg/broadcaster"
7 7
 	"github.com/docker/docker/pkg/reexec"
8 8
 )
9 9
 
... ...
@@ -13,8 +13,8 @@ func init() {
13 13
 
14 14
 func TestPools(t *testing.T) {
15 15
 	s := &TagStore{
16
-		pullingPool: make(map[string]*progressreader.Broadcaster),
17
-		pushingPool: make(map[string]*progressreader.Broadcaster),
16
+		pullingPool: make(map[string]*broadcaster.Buffered),
17
+		pushingPool: make(map[string]*broadcaster.Buffered),
18 18
 	}
19 19
 
20 20
 	if _, found := s.poolAdd("pull", "test1"); found {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/distribution/digest"
12 12
 	"github.com/docker/distribution/manifest"
13 13
 	"github.com/docker/docker/image"
14
+	"github.com/docker/docker/pkg/broadcaster"
14 15
 	"github.com/docker/docker/pkg/progressreader"
15 16
 	"github.com/docker/docker/pkg/streamformatter"
16 17
 	"github.com/docker/docker/pkg/stringid"
... ...
@@ -110,7 +111,7 @@ type downloadInfo struct {
110 110
 	size        int64
111 111
 	err         chan error
112 112
 	poolKey     string
113
-	broadcaster *progressreader.Broadcaster
113
+	broadcaster *broadcaster.Buffered
114 114
 }
115 115
 
116 116
 type errVerification struct{}
... ...
@@ -16,8 +16,8 @@ import (
16 16
 	"github.com/docker/docker/daemon/events"
17 17
 	"github.com/docker/docker/graph/tags"
18 18
 	"github.com/docker/docker/image"
19
+	"github.com/docker/docker/pkg/broadcaster"
19 20
 	"github.com/docker/docker/pkg/parsers"
20
-	"github.com/docker/docker/pkg/progressreader"
21 21
 	"github.com/docker/docker/pkg/stringid"
22 22
 	"github.com/docker/docker/registry"
23 23
 	"github.com/docker/docker/trust"
... ...
@@ -37,8 +37,8 @@ type TagStore struct {
37 37
 	sync.Mutex
38 38
 	// FIXME: move push/pull-related fields
39 39
 	// to a helper type
40
-	pullingPool     map[string]*progressreader.Broadcaster
41
-	pushingPool     map[string]*progressreader.Broadcaster
40
+	pullingPool     map[string]*broadcaster.Buffered
41
+	pushingPool     map[string]*broadcaster.Buffered
42 42
 	registryService *registry.Service
43 43
 	eventsService   *events.Events
44 44
 	trustService    *trust.Store
... ...
@@ -94,8 +94,8 @@ func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) {
94 94
 		graph:           cfg.Graph,
95 95
 		trustKey:        cfg.Key,
96 96
 		Repositories:    make(map[string]Repository),
97
-		pullingPool:     make(map[string]*progressreader.Broadcaster),
98
-		pushingPool:     make(map[string]*progressreader.Broadcaster),
97
+		pullingPool:     make(map[string]*broadcaster.Buffered),
98
+		pushingPool:     make(map[string]*broadcaster.Buffered),
99 99
 		registryService: cfg.Registry,
100 100
 		eventsService:   cfg.Events,
101 101
 		trustService:    cfg.Trust,
... ...
@@ -437,7 +437,7 @@ func validateDigest(dgst string) error {
437 437
 // poolAdd checks if a push or pull is already running, and returns
438 438
 // (broadcaster, true) if a running operation is found. Otherwise, it creates a
439 439
 // new one and returns (broadcaster, false).
440
-func (store *TagStore) poolAdd(kind, key string) (*progressreader.Broadcaster, bool) {
440
+func (store *TagStore) poolAdd(kind, key string) (*broadcaster.Buffered, bool) {
441 441
 	store.Lock()
442 442
 	defer store.Unlock()
443 443
 
... ...
@@ -448,7 +448,7 @@ func (store *TagStore) poolAdd(kind, key string) (*progressreader.Broadcaster, b
448 448
 		return p, true
449 449
 	}
450 450
 
451
-	broadcaster := progressreader.NewBroadcaster()
451
+	broadcaster := broadcaster.NewBuffered()
452 452
 
453 453
 	switch kind {
454 454
 	case "pull":
455 455
new file mode 100644
... ...
@@ -0,0 +1,167 @@
0
+package broadcaster
1
+
2
+import (
3
+	"errors"
4
+	"io"
5
+	"sync"
6
+)
7
+
8
+// Buffered keeps track of one or more observers watching the progress
9
+// of an operation. For example, if multiple clients are trying to pull an
10
+// image, they share a Buffered struct for the download operation.
11
+type Buffered struct {
12
+	sync.Mutex
13
+	// c is a channel that observers block on, waiting for the operation
14
+	// to finish.
15
+	c chan struct{}
16
+	// cond is a condition variable used to wake up observers when there's
17
+	// new data available.
18
+	cond *sync.Cond
19
+	// history is a buffer of the progress output so far, so a new observer
20
+	// can catch up. The history is stored as a slice of separate byte
21
+	// slices, so that if the writer is a WriteFlusher, the flushes will
22
+	// happen in the right places.
23
+	history [][]byte
24
+	// wg is a WaitGroup used to wait for all writes to finish on Close
25
+	wg sync.WaitGroup
26
+	// result is the argument passed to the first call of Close, and
27
+	// returned to callers of Wait
28
+	result error
29
+}
30
+
31
+// NewBuffered returns an initialized Buffered structure.
32
+func NewBuffered() *Buffered {
33
+	b := &Buffered{
34
+		c: make(chan struct{}),
35
+	}
36
+	b.cond = sync.NewCond(b)
37
+	return b
38
+}
39
+
40
+// closed returns true if and only if the broadcaster has been closed
41
+func (broadcaster *Buffered) closed() bool {
42
+	select {
43
+	case <-broadcaster.c:
44
+		return true
45
+	default:
46
+		return false
47
+	}
48
+}
49
+
50
+// receiveWrites runs as a goroutine so that writes don't block the Write
51
+// function. It writes the new data in broadcaster.history each time there's
52
+// activity on the broadcaster.cond condition variable.
53
+func (broadcaster *Buffered) receiveWrites(observer io.Writer) {
54
+	n := 0
55
+
56
+	broadcaster.Lock()
57
+
58
+	// The condition variable wait is at the end of this loop, so that the
59
+	// first iteration will write the history so far.
60
+	for {
61
+		newData := broadcaster.history[n:]
62
+		// Make a copy of newData so we can release the lock
63
+		sendData := make([][]byte, len(newData), len(newData))
64
+		copy(sendData, newData)
65
+		broadcaster.Unlock()
66
+
67
+		for len(sendData) > 0 {
68
+			_, err := observer.Write(sendData[0])
69
+			if err != nil {
70
+				broadcaster.wg.Done()
71
+				return
72
+			}
73
+			n++
74
+			sendData = sendData[1:]
75
+		}
76
+
77
+		broadcaster.Lock()
78
+
79
+		// If we are behind, we need to catch up instead of waiting
80
+		// or handling a closure.
81
+		if len(broadcaster.history) != n {
82
+			continue
83
+		}
84
+
85
+		// detect closure of the broadcast writer
86
+		if broadcaster.closed() {
87
+			broadcaster.Unlock()
88
+			broadcaster.wg.Done()
89
+			return
90
+		}
91
+
92
+		broadcaster.cond.Wait()
93
+
94
+		// Mutex is still locked as the loop continues
95
+	}
96
+}
97
+
98
+// Write adds data to the history buffer, and also writes it to all current
99
+// observers.
100
+func (broadcaster *Buffered) Write(p []byte) (n int, err error) {
101
+	broadcaster.Lock()
102
+	defer broadcaster.Unlock()
103
+
104
+	// Is the broadcaster closed? If so, the write should fail.
105
+	if broadcaster.closed() {
106
+		return 0, errors.New("attempted write to a closed broadcaster.Buffered")
107
+	}
108
+
109
+	// Add message in p to the history slice
110
+	newEntry := make([]byte, len(p), len(p))
111
+	copy(newEntry, p)
112
+	broadcaster.history = append(broadcaster.history, newEntry)
113
+
114
+	broadcaster.cond.Broadcast()
115
+
116
+	return len(p), nil
117
+}
118
+
119
+// Add adds an observer to the broadcaster. The new observer receives the
120
+// data from the history buffer, and also all subsequent data.
121
+func (broadcaster *Buffered) Add(w io.Writer) error {
122
+	// The lock is acquired here so that Add can't race with Close
123
+	broadcaster.Lock()
124
+	defer broadcaster.Unlock()
125
+
126
+	if broadcaster.closed() {
127
+		return errors.New("attempted to add observer to a closed broadcaster.Buffered")
128
+	}
129
+
130
+	broadcaster.wg.Add(1)
131
+	go broadcaster.receiveWrites(w)
132
+
133
+	return nil
134
+}
135
+
136
+// CloseWithError signals to all observers that the operation has finished. Its
137
+// argument is a result that should be returned to waiters blocking on Wait.
138
+func (broadcaster *Buffered) CloseWithError(result error) {
139
+	broadcaster.Lock()
140
+	if broadcaster.closed() {
141
+		broadcaster.Unlock()
142
+		return
143
+	}
144
+	broadcaster.result = result
145
+	close(broadcaster.c)
146
+	broadcaster.cond.Broadcast()
147
+	broadcaster.Unlock()
148
+
149
+	// Don't return until all writers have caught up.
150
+	broadcaster.wg.Wait()
151
+}
152
+
153
+// Close signals to all observers that the operation has finished. It causes
154
+// all calls to Wait to return nil.
155
+func (broadcaster *Buffered) Close() {
156
+	broadcaster.CloseWithError(nil)
157
+}
158
+
159
+// Wait blocks until the operation is marked as completed by the Close method,
160
+// and all writer goroutines have completed. It returns the argument that was
161
+// passed to Close.
162
+func (broadcaster *Buffered) Wait() error {
163
+	<-broadcaster.c
164
+	broadcaster.wg.Wait()
165
+	return broadcaster.result
166
+}
0 167
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package broadcaster
1
+
2
+import (
3
+	"io"
4
+	"sync"
5
+)
6
+
7
+// Unbuffered accumulates multiple io.WriteCloser by stream.
8
+type Unbuffered struct {
9
+	mu      sync.Mutex
10
+	writers []io.WriteCloser
11
+}
12
+
13
+// Add adds new io.WriteCloser.
14
+func (w *Unbuffered) Add(writer io.WriteCloser) {
15
+	w.mu.Lock()
16
+	w.writers = append(w.writers, writer)
17
+	w.mu.Unlock()
18
+}
19
+
20
+// Write writes bytes to all writers. Failed writers will be evicted during
21
+// this call.
22
+func (w *Unbuffered) Write(p []byte) (n int, err error) {
23
+	w.mu.Lock()
24
+	var evict []int
25
+	for i, sw := range w.writers {
26
+		if n, err := sw.Write(p); err != nil || n != len(p) {
27
+			// On error, evict the writer
28
+			evict = append(evict, i)
29
+		}
30
+	}
31
+	for n, i := range evict {
32
+		w.writers = append(w.writers[:i-n], w.writers[i-n+1:]...)
33
+	}
34
+	w.mu.Unlock()
35
+	return len(p), nil
36
+}
37
+
38
+// Clean closes and removes all writers. Last non-eol-terminated part of data
39
+// will be saved.
40
+func (w *Unbuffered) Clean() error {
41
+	w.mu.Lock()
42
+	for _, sw := range w.writers {
43
+		sw.Close()
44
+	}
45
+	w.writers = nil
46
+	w.mu.Unlock()
47
+	return nil
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,162 @@
0
+package broadcaster
1
+
2
+import (
3
+	"bytes"
4
+	"errors"
5
+	"strings"
6
+
7
+	"testing"
8
+)
9
+
10
+type dummyWriter struct {
11
+	buffer      bytes.Buffer
12
+	failOnWrite bool
13
+}
14
+
15
+func (dw *dummyWriter) Write(p []byte) (n int, err error) {
16
+	if dw.failOnWrite {
17
+		return 0, errors.New("Fake fail")
18
+	}
19
+	return dw.buffer.Write(p)
20
+}
21
+
22
+func (dw *dummyWriter) String() string {
23
+	return dw.buffer.String()
24
+}
25
+
26
+func (dw *dummyWriter) Close() error {
27
+	return nil
28
+}
29
+
30
+func TestUnbuffered(t *testing.T) {
31
+	writer := new(Unbuffered)
32
+
33
+	// Test 1: Both bufferA and bufferB should contain "foo"
34
+	bufferA := &dummyWriter{}
35
+	writer.Add(bufferA)
36
+	bufferB := &dummyWriter{}
37
+	writer.Add(bufferB)
38
+	writer.Write([]byte("foo"))
39
+
40
+	if bufferA.String() != "foo" {
41
+		t.Errorf("Buffer contains %v", bufferA.String())
42
+	}
43
+
44
+	if bufferB.String() != "foo" {
45
+		t.Errorf("Buffer contains %v", bufferB.String())
46
+	}
47
+
48
+	// Test2: bufferA and bufferB should contain "foobar",
49
+	// while bufferC should only contain "bar"
50
+	bufferC := &dummyWriter{}
51
+	writer.Add(bufferC)
52
+	writer.Write([]byte("bar"))
53
+
54
+	if bufferA.String() != "foobar" {
55
+		t.Errorf("Buffer contains %v", bufferA.String())
56
+	}
57
+
58
+	if bufferB.String() != "foobar" {
59
+		t.Errorf("Buffer contains %v", bufferB.String())
60
+	}
61
+
62
+	if bufferC.String() != "bar" {
63
+		t.Errorf("Buffer contains %v", bufferC.String())
64
+	}
65
+
66
+	// Test3: Test eviction on failure
67
+	bufferA.failOnWrite = true
68
+	writer.Write([]byte("fail"))
69
+	if bufferA.String() != "foobar" {
70
+		t.Errorf("Buffer contains %v", bufferA.String())
71
+	}
72
+	if bufferC.String() != "barfail" {
73
+		t.Errorf("Buffer contains %v", bufferC.String())
74
+	}
75
+	// Even though we reset the flag, no more writes should go in there
76
+	bufferA.failOnWrite = false
77
+	writer.Write([]byte("test"))
78
+	if bufferA.String() != "foobar" {
79
+		t.Errorf("Buffer contains %v", bufferA.String())
80
+	}
81
+	if bufferC.String() != "barfailtest" {
82
+		t.Errorf("Buffer contains %v", bufferC.String())
83
+	}
84
+
85
+	// Test4: Test eviction on multiple simultaneous failures
86
+	bufferB.failOnWrite = true
87
+	bufferC.failOnWrite = true
88
+	bufferD := &dummyWriter{}
89
+	writer.Add(bufferD)
90
+	writer.Write([]byte("yo"))
91
+	writer.Write([]byte("ink"))
92
+	if strings.Contains(bufferB.String(), "yoink") {
93
+		t.Errorf("bufferB received write. contents: %q", bufferB)
94
+	}
95
+	if strings.Contains(bufferC.String(), "yoink") {
96
+		t.Errorf("bufferC received write. contents: %q", bufferC)
97
+	}
98
+	if g, w := bufferD.String(), "yoink"; g != w {
99
+		t.Errorf("bufferD = %q, want %q", g, w)
100
+	}
101
+
102
+	writer.Clean()
103
+}
104
+
105
+type devNullCloser int
106
+
107
+func (d devNullCloser) Close() error {
108
+	return nil
109
+}
110
+
111
+func (d devNullCloser) Write(buf []byte) (int, error) {
112
+	return len(buf), nil
113
+}
114
+
115
+// This test checks for races. It is only useful when run with the race detector.
116
+func TestRaceUnbuffered(t *testing.T) {
117
+	writer := new(Unbuffered)
118
+	c := make(chan bool)
119
+	go func() {
120
+		writer.Add(devNullCloser(0))
121
+		c <- true
122
+	}()
123
+	writer.Write([]byte("hello"))
124
+	<-c
125
+}
126
+
127
+func BenchmarkUnbuffered(b *testing.B) {
128
+	writer := new(Unbuffered)
129
+	setUpWriter := func() {
130
+		for i := 0; i < 100; i++ {
131
+			writer.Add(devNullCloser(0))
132
+			writer.Add(devNullCloser(0))
133
+			writer.Add(devNullCloser(0))
134
+		}
135
+	}
136
+	testLine := "Line that thinks that it is log line from docker"
137
+	var buf bytes.Buffer
138
+	for i := 0; i < 100; i++ {
139
+		buf.Write([]byte(testLine + "\n"))
140
+	}
141
+	// line without eol
142
+	buf.Write([]byte(testLine))
143
+	testText := buf.Bytes()
144
+	b.SetBytes(int64(5 * len(testText)))
145
+	b.ResetTimer()
146
+	for i := 0; i < b.N; i++ {
147
+		b.StopTimer()
148
+		setUpWriter()
149
+		b.StartTimer()
150
+
151
+		for j := 0; j < 5; j++ {
152
+			if _, err := writer.Write(testText); err != nil {
153
+				b.Fatal(err)
154
+			}
155
+		}
156
+
157
+		b.StopTimer()
158
+		writer.Clean()
159
+		b.StartTimer()
160
+	}
161
+}
0 162
deleted file mode 100644
... ...
@@ -1,54 +0,0 @@
1
-package broadcastwriter
2
-
3
-import (
4
-	"io"
5
-	"sync"
6
-)
7
-
8
-// BroadcastWriter accumulate multiple io.WriteCloser by stream.
9
-type BroadcastWriter struct {
10
-	mu      sync.Mutex
11
-	writers []io.WriteCloser
12
-}
13
-
14
-// AddWriter adds new io.WriteCloser.
15
-func (w *BroadcastWriter) AddWriter(writer io.WriteCloser) {
16
-	w.mu.Lock()
17
-	w.writers = append(w.writers, writer)
18
-	w.mu.Unlock()
19
-}
20
-
21
-// Write writes bytes to all writers. Failed writers will be evicted during
22
-// this call.
23
-func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
24
-	w.mu.Lock()
25
-	var evict []int
26
-	for i, sw := range w.writers {
27
-		if n, err := sw.Write(p); err != nil || n != len(p) {
28
-			// On error, evict the writer
29
-			evict = append(evict, i)
30
-		}
31
-	}
32
-	for n, i := range evict {
33
-		w.writers = append(w.writers[:i-n], w.writers[i-n+1:]...)
34
-	}
35
-	w.mu.Unlock()
36
-	return len(p), nil
37
-}
38
-
39
-// Clean closes and removes all writers. Last non-eol-terminated part of data
40
-// will be saved.
41
-func (w *BroadcastWriter) Clean() error {
42
-	w.mu.Lock()
43
-	for _, sw := range w.writers {
44
-		sw.Close()
45
-	}
46
-	w.writers = nil
47
-	w.mu.Unlock()
48
-	return nil
49
-}
50
-
51
-// New creates a new BroadcastWriter.
52
-func New() *BroadcastWriter {
53
-	return &BroadcastWriter{}
54
-}
55 1
deleted file mode 100644
... ...
@@ -1,162 +0,0 @@
1
-package broadcastwriter
2
-
3
-import (
4
-	"bytes"
5
-	"errors"
6
-	"strings"
7
-
8
-	"testing"
9
-)
10
-
11
-type dummyWriter struct {
12
-	buffer      bytes.Buffer
13
-	failOnWrite bool
14
-}
15
-
16
-func (dw *dummyWriter) Write(p []byte) (n int, err error) {
17
-	if dw.failOnWrite {
18
-		return 0, errors.New("Fake fail")
19
-	}
20
-	return dw.buffer.Write(p)
21
-}
22
-
23
-func (dw *dummyWriter) String() string {
24
-	return dw.buffer.String()
25
-}
26
-
27
-func (dw *dummyWriter) Close() error {
28
-	return nil
29
-}
30
-
31
-func TestBroadcastWriter(t *testing.T) {
32
-	writer := New()
33
-
34
-	// Test 1: Both bufferA and bufferB should contain "foo"
35
-	bufferA := &dummyWriter{}
36
-	writer.AddWriter(bufferA)
37
-	bufferB := &dummyWriter{}
38
-	writer.AddWriter(bufferB)
39
-	writer.Write([]byte("foo"))
40
-
41
-	if bufferA.String() != "foo" {
42
-		t.Errorf("Buffer contains %v", bufferA.String())
43
-	}
44
-
45
-	if bufferB.String() != "foo" {
46
-		t.Errorf("Buffer contains %v", bufferB.String())
47
-	}
48
-
49
-	// Test2: bufferA and bufferB should contain "foobar",
50
-	// while bufferC should only contain "bar"
51
-	bufferC := &dummyWriter{}
52
-	writer.AddWriter(bufferC)
53
-	writer.Write([]byte("bar"))
54
-
55
-	if bufferA.String() != "foobar" {
56
-		t.Errorf("Buffer contains %v", bufferA.String())
57
-	}
58
-
59
-	if bufferB.String() != "foobar" {
60
-		t.Errorf("Buffer contains %v", bufferB.String())
61
-	}
62
-
63
-	if bufferC.String() != "bar" {
64
-		t.Errorf("Buffer contains %v", bufferC.String())
65
-	}
66
-
67
-	// Test3: Test eviction on failure
68
-	bufferA.failOnWrite = true
69
-	writer.Write([]byte("fail"))
70
-	if bufferA.String() != "foobar" {
71
-		t.Errorf("Buffer contains %v", bufferA.String())
72
-	}
73
-	if bufferC.String() != "barfail" {
74
-		t.Errorf("Buffer contains %v", bufferC.String())
75
-	}
76
-	// Even though we reset the flag, no more writes should go in there
77
-	bufferA.failOnWrite = false
78
-	writer.Write([]byte("test"))
79
-	if bufferA.String() != "foobar" {
80
-		t.Errorf("Buffer contains %v", bufferA.String())
81
-	}
82
-	if bufferC.String() != "barfailtest" {
83
-		t.Errorf("Buffer contains %v", bufferC.String())
84
-	}
85
-
86
-	// Test4: Test eviction on multiple simultaneous failures
87
-	bufferB.failOnWrite = true
88
-	bufferC.failOnWrite = true
89
-	bufferD := &dummyWriter{}
90
-	writer.AddWriter(bufferD)
91
-	writer.Write([]byte("yo"))
92
-	writer.Write([]byte("ink"))
93
-	if strings.Contains(bufferB.String(), "yoink") {
94
-		t.Errorf("bufferB received write. contents: %q", bufferB)
95
-	}
96
-	if strings.Contains(bufferC.String(), "yoink") {
97
-		t.Errorf("bufferC received write. contents: %q", bufferC)
98
-	}
99
-	if g, w := bufferD.String(), "yoink"; g != w {
100
-		t.Errorf("bufferD = %q, want %q", g, w)
101
-	}
102
-
103
-	writer.Clean()
104
-}
105
-
106
-type devNullCloser int
107
-
108
-func (d devNullCloser) Close() error {
109
-	return nil
110
-}
111
-
112
-func (d devNullCloser) Write(buf []byte) (int, error) {
113
-	return len(buf), nil
114
-}
115
-
116
-// This test checks for races. It is only useful when run with the race detector.
117
-func TestRaceBroadcastWriter(t *testing.T) {
118
-	writer := New()
119
-	c := make(chan bool)
120
-	go func() {
121
-		writer.AddWriter(devNullCloser(0))
122
-		c <- true
123
-	}()
124
-	writer.Write([]byte("hello"))
125
-	<-c
126
-}
127
-
128
-func BenchmarkBroadcastWriter(b *testing.B) {
129
-	writer := New()
130
-	setUpWriter := func() {
131
-		for i := 0; i < 100; i++ {
132
-			writer.AddWriter(devNullCloser(0))
133
-			writer.AddWriter(devNullCloser(0))
134
-			writer.AddWriter(devNullCloser(0))
135
-		}
136
-	}
137
-	testLine := "Line that thinks that it is log line from docker"
138
-	var buf bytes.Buffer
139
-	for i := 0; i < 100; i++ {
140
-		buf.Write([]byte(testLine + "\n"))
141
-	}
142
-	// line without eol
143
-	buf.Write([]byte(testLine))
144
-	testText := buf.Bytes()
145
-	b.SetBytes(int64(5 * len(testText)))
146
-	b.ResetTimer()
147
-	for i := 0; i < b.N; i++ {
148
-		b.StopTimer()
149
-		setUpWriter()
150
-		b.StartTimer()
151
-
152
-		for j := 0; j < 5; j++ {
153
-			if _, err := writer.Write(testText); err != nil {
154
-				b.Fatal(err)
155
-			}
156
-		}
157
-
158
-		b.StopTimer()
159
-		writer.Clean()
160
-		b.StartTimer()
161
-	}
162
-}
163 1
deleted file mode 100644
... ...
@@ -1,167 +0,0 @@
1
-package progressreader
2
-
3
-import (
4
-	"errors"
5
-	"io"
6
-	"sync"
7
-)
8
-
9
-// Broadcaster keeps track of one or more observers watching the progress
10
-// of an operation. For example, if multiple clients are trying to pull an
11
-// image, they share a Broadcaster for the download operation.
12
-type Broadcaster struct {
13
-	sync.Mutex
14
-	// c is a channel that observers block on, waiting for the operation
15
-	// to finish.
16
-	c chan struct{}
17
-	// cond is a condition variable used to wake up observers when there's
18
-	// new data available.
19
-	cond *sync.Cond
20
-	// history is a buffer of the progress output so far, so a new observer
21
-	// can catch up. The history is stored as a slice of separate byte
22
-	// slices, so that if the writer is a WriteFlusher, the flushes will
23
-	// happen in the right places.
24
-	history [][]byte
25
-	// wg is a WaitGroup used to wait for all writes to finish on Close
26
-	wg sync.WaitGroup
27
-	// result is the argument passed to the first call of Close, and
28
-	// returned to callers of Wait
29
-	result error
30
-}
31
-
32
-// NewBroadcaster returns a Broadcaster structure
33
-func NewBroadcaster() *Broadcaster {
34
-	b := &Broadcaster{
35
-		c: make(chan struct{}),
36
-	}
37
-	b.cond = sync.NewCond(b)
38
-	return b
39
-}
40
-
41
-// closed returns true if and only if the broadcaster has been closed
42
-func (broadcaster *Broadcaster) closed() bool {
43
-	select {
44
-	case <-broadcaster.c:
45
-		return true
46
-	default:
47
-		return false
48
-	}
49
-}
50
-
51
-// receiveWrites runs as a goroutine so that writes don't block the Write
52
-// function. It writes the new data in broadcaster.history each time there's
53
-// activity on the broadcaster.cond condition variable.
54
-func (broadcaster *Broadcaster) receiveWrites(observer io.Writer) {
55
-	n := 0
56
-
57
-	broadcaster.Lock()
58
-
59
-	// The condition variable wait is at the end of this loop, so that the
60
-	// first iteration will write the history so far.
61
-	for {
62
-		newData := broadcaster.history[n:]
63
-		// Make a copy of newData so we can release the lock
64
-		sendData := make([][]byte, len(newData), len(newData))
65
-		copy(sendData, newData)
66
-		broadcaster.Unlock()
67
-
68
-		for len(sendData) > 0 {
69
-			_, err := observer.Write(sendData[0])
70
-			if err != nil {
71
-				broadcaster.wg.Done()
72
-				return
73
-			}
74
-			n++
75
-			sendData = sendData[1:]
76
-		}
77
-
78
-		broadcaster.Lock()
79
-
80
-		// If we are behind, we need to catch up instead of waiting
81
-		// or handling a closure.
82
-		if len(broadcaster.history) != n {
83
-			continue
84
-		}
85
-
86
-		// detect closure of the broadcast writer
87
-		if broadcaster.closed() {
88
-			broadcaster.Unlock()
89
-			broadcaster.wg.Done()
90
-			return
91
-		}
92
-
93
-		broadcaster.cond.Wait()
94
-
95
-		// Mutex is still locked as the loop continues
96
-	}
97
-}
98
-
99
-// Write adds data to the history buffer, and also writes it to all current
100
-// observers.
101
-func (broadcaster *Broadcaster) Write(p []byte) (n int, err error) {
102
-	broadcaster.Lock()
103
-	defer broadcaster.Unlock()
104
-
105
-	// Is the broadcaster closed? If so, the write should fail.
106
-	if broadcaster.closed() {
107
-		return 0, errors.New("attempted write to closed progressreader Broadcaster")
108
-	}
109
-
110
-	// Add message in p to the history slice
111
-	newEntry := make([]byte, len(p), len(p))
112
-	copy(newEntry, p)
113
-	broadcaster.history = append(broadcaster.history, newEntry)
114
-
115
-	broadcaster.cond.Broadcast()
116
-
117
-	return len(p), nil
118
-}
119
-
120
-// Add adds an observer to the Broadcaster. The new observer receives the
121
-// data from the history buffer, and also all subsequent data.
122
-func (broadcaster *Broadcaster) Add(w io.Writer) error {
123
-	// The lock is acquired here so that Add can't race with Close
124
-	broadcaster.Lock()
125
-	defer broadcaster.Unlock()
126
-
127
-	if broadcaster.closed() {
128
-		return errors.New("attempted to add observer to closed progressreader Broadcaster")
129
-	}
130
-
131
-	broadcaster.wg.Add(1)
132
-	go broadcaster.receiveWrites(w)
133
-
134
-	return nil
135
-}
136
-
137
-// CloseWithError signals to all observers that the operation has finished. Its
138
-// argument is a result that should be returned to waiters blocking on Wait.
139
-func (broadcaster *Broadcaster) CloseWithError(result error) {
140
-	broadcaster.Lock()
141
-	if broadcaster.closed() {
142
-		broadcaster.Unlock()
143
-		return
144
-	}
145
-	broadcaster.result = result
146
-	close(broadcaster.c)
147
-	broadcaster.cond.Broadcast()
148
-	broadcaster.Unlock()
149
-
150
-	// Don't return until all writers have caught up.
151
-	broadcaster.wg.Wait()
152
-}
153
-
154
-// Close signals to all observers that the operation has finished. It causes
155
-// all calls to Wait to return nil.
156
-func (broadcaster *Broadcaster) Close() {
157
-	broadcaster.CloseWithError(nil)
158
-}
159
-
160
-// Wait blocks until the operation is marked as completed by the Close method,
161
-// and all writer goroutines have completed. It returns the argument that was
162
-// passed to Close.
163
-func (broadcaster *Broadcaster) Wait() error {
164
-	<-broadcaster.c
165
-	broadcaster.wg.Wait()
166
-	return broadcaster.result
167
-}