github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/distribution/xfer/transfer_test.go (about)

     1  package xfer // import "github.com/docker/docker/distribution/xfer"
     2  
     3  import (
     4  	"sync/atomic"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/docker/docker/pkg/progress"
     9  )
    10  
    11  func TestTransfer(t *testing.T) {
    12  	makeXferFunc := func(id string) doFunc {
    13  		return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer {
    14  			select {
    15  			case <-start:
    16  			default:
    17  				t.Errorf("%s: transfer function not started even though concurrency limit not reached", id)
    18  			}
    19  
    20  			xfer := newTransfer()
    21  			go func() {
    22  				for i := 0; i <= 10; i++ {
    23  					progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10}
    24  					time.Sleep(10 * time.Millisecond)
    25  				}
    26  				close(progressChan)
    27  			}()
    28  			return xfer
    29  		}
    30  	}
    31  
    32  	tm := newTransferManager(5)
    33  	progressChan := make(chan progress.Progress)
    34  	progressDone := make(chan struct{})
    35  	receivedProgress := make(map[string]int64)
    36  
    37  	go func() {
    38  		for p := range progressChan {
    39  			val, present := receivedProgress[p.ID]
    40  			if present && p.Current <= val {
    41  				t.Errorf("%s: got unexpected progress value: %d (expected <= %d)", p.ID, p.Current, val)
    42  			}
    43  			receivedProgress[p.ID] = p.Current
    44  		}
    45  		close(progressDone)
    46  	}()
    47  
    48  	// Start a few transfers
    49  	ids := []string{"id1", "id2", "id3"}
    50  	xfers := make([]transfer, len(ids))
    51  	watchers := make([]*watcher, len(ids))
    52  	for i, id := range ids {
    53  		xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan))
    54  	}
    55  
    56  	for i, xfer := range xfers {
    57  		<-xfer.done()
    58  		xfer.release(watchers[i])
    59  	}
    60  	close(progressChan)
    61  	<-progressDone
    62  
    63  	for _, id := range ids {
    64  		if receivedProgress[id] != 10 {
    65  			t.Fatalf("final progress value %d instead of 10", receivedProgress[id])
    66  		}
    67  	}
    68  }
    69  
    70  func TestConcurrencyLimit(t *testing.T) {
    71  	const concurrencyLimit = 3
    72  	var runningJobs int32
    73  
    74  	makeXferFunc := func(id string) doFunc {
    75  		return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer {
    76  			xfer := newTransfer()
    77  			go func() {
    78  				<-start
    79  				totalJobs := atomic.AddInt32(&runningJobs, 1)
    80  				if int(totalJobs) > concurrencyLimit {
    81  					t.Errorf("%s: too many jobs running (%d > %d)", id, totalJobs, concurrencyLimit)
    82  				}
    83  				for i := 0; i <= 10; i++ {
    84  					progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10}
    85  					time.Sleep(10 * time.Millisecond)
    86  				}
    87  				atomic.AddInt32(&runningJobs, -1)
    88  				close(progressChan)
    89  			}()
    90  			return xfer
    91  		}
    92  	}
    93  
    94  	tm := newTransferManager(concurrencyLimit)
    95  	progressChan := make(chan progress.Progress)
    96  	progressDone := make(chan struct{})
    97  	receivedProgress := make(map[string]int64)
    98  
    99  	go func() {
   100  		for p := range progressChan {
   101  			receivedProgress[p.ID] = p.Current
   102  		}
   103  		close(progressDone)
   104  	}()
   105  
   106  	// Start more transfers than the concurrency limit
   107  	ids := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8"}
   108  	xfers := make([]transfer, len(ids))
   109  	watchers := make([]*watcher, len(ids))
   110  	for i, id := range ids {
   111  		xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan))
   112  	}
   113  
   114  	for i, xfer := range xfers {
   115  		<-xfer.done()
   116  		xfer.release(watchers[i])
   117  	}
   118  	close(progressChan)
   119  	<-progressDone
   120  
   121  	for _, id := range ids {
   122  		if receivedProgress[id] != 10 {
   123  			t.Fatalf("final progress value %d instead of 10", receivedProgress[id])
   124  		}
   125  	}
   126  }
   127  
   128  func TestInactiveJobs(t *testing.T) {
   129  	const concurrencyLimit = 3
   130  	var runningJobs int32
   131  	testDone := make(chan struct{})
   132  
   133  	makeXferFunc := func(id string) doFunc {
   134  		return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) transfer {
   135  			xfer := newTransfer()
   136  			go func() {
   137  				<-start
   138  				totalJobs := atomic.AddInt32(&runningJobs, 1)
   139  				if int(totalJobs) > concurrencyLimit {
   140  					t.Errorf("%s: too many jobs running (%d > %d)", id, totalJobs, concurrencyLimit)
   141  				}
   142  				for i := 0; i <= 10; i++ {
   143  					progressChan <- progress.Progress{ID: id, Action: "testing", Current: int64(i), Total: 10}
   144  					time.Sleep(10 * time.Millisecond)
   145  				}
   146  				atomic.AddInt32(&runningJobs, -1)
   147  				close(inactive)
   148  				<-testDone
   149  				close(progressChan)
   150  			}()
   151  			return xfer
   152  		}
   153  	}
   154  
   155  	tm := newTransferManager(concurrencyLimit)
   156  	progressChan := make(chan progress.Progress)
   157  	progressDone := make(chan struct{})
   158  	receivedProgress := make(map[string]int64)
   159  
   160  	go func() {
   161  		for p := range progressChan {
   162  			receivedProgress[p.ID] = p.Current
   163  		}
   164  		close(progressDone)
   165  	}()
   166  
   167  	// Start more transfers than the concurrency limit
   168  	ids := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8"}
   169  	xfers := make([]transfer, len(ids))
   170  	watchers := make([]*watcher, len(ids))
   171  	for i, id := range ids {
   172  		xfers[i], watchers[i] = tm.transfer(id, makeXferFunc(id), progress.ChanOutput(progressChan))
   173  	}
   174  
   175  	close(testDone)
   176  	for i, xfer := range xfers {
   177  		<-xfer.done()
   178  		xfer.release(watchers[i])
   179  	}
   180  	close(progressChan)
   181  	<-progressDone
   182  
   183  	for _, id := range ids {
   184  		if receivedProgress[id] != 10 {
   185  			t.Fatalf("final progress value %d instead of 10", receivedProgress[id])
   186  		}
   187  	}
   188  }
   189  
   190  func TestWatchRelease(t *testing.T) {
   191  	ready := make(chan struct{})
   192  
   193  	makeXferFunc := func(id string) doFunc {
   194  		return func(progressChan chan<- progress.Progress, start <-chan struct{}, _ chan<- struct{}) transfer {
   195  			xfer := newTransfer()
   196  			go func() {
   197  				defer func() {
   198  					close(progressChan)
   199  				}()
   200  				<-ready
   201  				for i := int64(0); ; i++ {
   202  					select {
   203  					case <-time.After(10 * time.Millisecond):
   204  					case <-xfer.context().Done():
   205  						return
   206  					}
   207  					progressChan <- progress.Progress{ID: id, Action: "testing", Current: i, Total: 10}
   208  				}
   209  			}()
   210  			return xfer
   211  		}
   212  	}
   213  
   214  	tm := newTransferManager(5)
   215  
   216  	type watcherInfo struct {
   217  		watcher               *watcher
   218  		progressChan          chan progress.Progress
   219  		progressDone          chan struct{}
   220  		receivedFirstProgress chan struct{}
   221  	}
   222  
   223  	progressConsumer := func(w watcherInfo) {
   224  		first := true
   225  		for range w.progressChan {
   226  			if first {
   227  				close(w.receivedFirstProgress)
   228  			}
   229  			first = false
   230  		}
   231  		close(w.progressDone)
   232  	}
   233  
   234  	// Start a transfer
   235  	watchers := make([]watcherInfo, 5)
   236  	var xfer transfer
   237  	watchers[0].progressChan = make(chan progress.Progress)
   238  	watchers[0].progressDone = make(chan struct{})
   239  	watchers[0].receivedFirstProgress = make(chan struct{})
   240  	xfer, watchers[0].watcher = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(watchers[0].progressChan))
   241  	go progressConsumer(watchers[0])
   242  
   243  	// Give it multiple watchers
   244  	for i := 1; i != len(watchers); i++ {
   245  		watchers[i].progressChan = make(chan progress.Progress)
   246  		watchers[i].progressDone = make(chan struct{})
   247  		watchers[i].receivedFirstProgress = make(chan struct{})
   248  		watchers[i].watcher = xfer.watch(progress.ChanOutput(watchers[i].progressChan))
   249  		go progressConsumer(watchers[i])
   250  	}
   251  
   252  	// Now that the watchers are set up, allow the transfer goroutine to
   253  	// proceed.
   254  	close(ready)
   255  
   256  	// Confirm that each watcher gets progress output.
   257  	for _, w := range watchers {
   258  		<-w.receivedFirstProgress
   259  	}
   260  
   261  	// Release one watcher every 5ms
   262  	for _, w := range watchers {
   263  		xfer.release(w.watcher)
   264  		<-time.After(5 * time.Millisecond)
   265  	}
   266  
   267  	// Now that all watchers have been released, Released() should
   268  	// return a closed channel.
   269  	<-xfer.released()
   270  
   271  	// Done() should return a closed channel because the xfer func returned
   272  	// due to cancellation.
   273  	<-xfer.done()
   274  
   275  	for _, w := range watchers {
   276  		close(w.progressChan)
   277  		<-w.progressDone
   278  	}
   279  }
   280  
   281  func TestWatchFinishedTransfer(t *testing.T) {
   282  	makeXferFunc := func(id string) doFunc {
   283  		return func(progressChan chan<- progress.Progress, _ <-chan struct{}, _ chan<- struct{}) transfer {
   284  			xfer := newTransfer()
   285  			go func() {
   286  				// Finish immediately
   287  				close(progressChan)
   288  			}()
   289  			return xfer
   290  		}
   291  	}
   292  
   293  	tm := newTransferManager(5)
   294  
   295  	// Start a transfer
   296  	watchers := make([]*watcher, 3)
   297  	var xfer transfer
   298  	xfer, watchers[0] = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(make(chan progress.Progress)))
   299  
   300  	// Give it a watcher immediately
   301  	watchers[1] = xfer.watch(progress.ChanOutput(make(chan progress.Progress)))
   302  
   303  	// Wait for the transfer to complete
   304  	<-xfer.done()
   305  
   306  	// Set up another watcher
   307  	watchers[2] = xfer.watch(progress.ChanOutput(make(chan progress.Progress)))
   308  
   309  	// Release the watchers
   310  	for _, w := range watchers {
   311  		xfer.release(w)
   312  	}
   313  
   314  	// Now that all watchers have been released, Released() should
   315  	// return a closed channel.
   316  	<-xfer.released()
   317  }
   318  
   319  func TestDuplicateTransfer(t *testing.T) {
   320  	ready := make(chan struct{})
   321  
   322  	var xferFuncCalls int32
   323  
   324  	makeXferFunc := func(id string) doFunc {
   325  		return func(progressChan chan<- progress.Progress, _ <-chan struct{}, _ chan<- struct{}) transfer {
   326  			atomic.AddInt32(&xferFuncCalls, 1)
   327  			xfer := newTransfer()
   328  			go func() {
   329  				defer func() {
   330  					close(progressChan)
   331  				}()
   332  				<-ready
   333  				for i := int64(0); ; i++ {
   334  					select {
   335  					case <-time.After(10 * time.Millisecond):
   336  					case <-xfer.context().Done():
   337  						return
   338  					}
   339  					progressChan <- progress.Progress{ID: id, Action: "testing", Current: i, Total: 10}
   340  				}
   341  			}()
   342  			return xfer
   343  		}
   344  	}
   345  
   346  	tm := newTransferManager(5)
   347  
   348  	type transferInfo struct {
   349  		xfer                  transfer
   350  		watcher               *watcher
   351  		progressChan          chan progress.Progress
   352  		progressDone          chan struct{}
   353  		receivedFirstProgress chan struct{}
   354  	}
   355  
   356  	progressConsumer := func(t transferInfo) {
   357  		first := true
   358  		for range t.progressChan {
   359  			if first {
   360  				close(t.receivedFirstProgress)
   361  			}
   362  			first = false
   363  		}
   364  		close(t.progressDone)
   365  	}
   366  
   367  	// Try to start multiple transfers with the same ID
   368  	transfers := make([]transferInfo, 5)
   369  	for i := range transfers {
   370  		t := &transfers[i]
   371  		t.progressChan = make(chan progress.Progress)
   372  		t.progressDone = make(chan struct{})
   373  		t.receivedFirstProgress = make(chan struct{})
   374  		t.xfer, t.watcher = tm.transfer("id1", makeXferFunc("id1"), progress.ChanOutput(t.progressChan))
   375  		go progressConsumer(*t)
   376  	}
   377  
   378  	// Allow the transfer goroutine to proceed.
   379  	close(ready)
   380  
   381  	// Confirm that each watcher gets progress output.
   382  	for _, t := range transfers {
   383  		<-t.receivedFirstProgress
   384  	}
   385  
   386  	// Confirm that the transfer function was called exactly once.
   387  	if xferFuncCalls != 1 {
   388  		t.Fatal("transfer function wasn't called exactly once")
   389  	}
   390  
   391  	// Release one watcher every 5ms
   392  	for _, t := range transfers {
   393  		t.xfer.release(t.watcher)
   394  		<-time.After(5 * time.Millisecond)
   395  	}
   396  
   397  	for _, t := range transfers {
   398  		// Now that all watchers have been released, Released() should
   399  		// return a closed channel.
   400  		<-t.xfer.released()
   401  		// Done() should return a closed channel because the xfer func returned
   402  		// due to cancellation.
   403  		<-t.xfer.done()
   404  	}
   405  
   406  	for _, t := range transfers {
   407  		close(t.progressChan)
   408  		<-t.progressDone
   409  	}
   410  }