github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/lang/channel/channel_test.go (about)

     1  // Copyright 2023 ByteDance Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package channel
    16  
    17  import (
    18  	"fmt"
    19  	"runtime"
    20  	"sync"
    21  	"sync/atomic"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func tlogf(t *testing.T, format string, args ...interface{}) {
    29  	t.Log(fmt.Sprintf("[%v] %s", time.Now().UTC(), fmt.Sprintf(format, args...)))
    30  }
    31  
    32  //go:noinline
    33  func factorial(x int) int {
    34  	if x <= 1 {
    35  		return x
    36  	}
    37  	return x * factorial(x-1)
    38  }
    39  
    40  var benchSizes = []int{1, 10, 100, 1000, -1}
    41  
    42  func BenchmarkNativeChan(b *testing.B) {
    43  	for _, size := range benchSizes {
    44  		if size < 0 {
    45  			continue
    46  		}
    47  		b.Run(fmt.Sprintf("Size-[%d]", size), func(b *testing.B) {
    48  			ch := make(chan interface{}, size)
    49  			b.RunParallel(func(pb *testing.PB) {
    50  				n := 0
    51  				for pb.Next() {
    52  					n++
    53  					ch <- n
    54  					<-ch
    55  				}
    56  			})
    57  		})
    58  	}
    59  }
    60  
    61  func BenchmarkChannel(b *testing.B) {
    62  	for _, size := range benchSizes {
    63  		b.Run(fmt.Sprintf("Size-[%d]", size), func(b *testing.B) {
    64  			var ch Channel
    65  			if size < 0 {
    66  				ch = New(WithNonBlock())
    67  			} else {
    68  				ch = New(WithSize(size))
    69  			}
    70  			defer ch.Close()
    71  			b.RunParallel(func(pb *testing.PB) {
    72  				n := 0
    73  				for pb.Next() {
    74  					n++
    75  					ch.Input(n)
    76  					<-ch.Output()
    77  				}
    78  			})
    79  		})
    80  	}
    81  }
    82  
    83  func TestChannelDefaultSize(t *testing.T) {
    84  	ch := New()
    85  	defer ch.Close()
    86  
    87  	ch.Input(0)
    88  	ch.Input(0)
    89  	var timeouted uint32
    90  	go func() {
    91  		ch.Input(0) // block
    92  		atomic.AddUint32(&timeouted, 1)
    93  	}()
    94  	go func() {
    95  		ch.Input(0) // block
    96  		atomic.AddUint32(&timeouted, 1)
    97  	}()
    98  	time.Sleep(time.Millisecond * 100)
    99  	assert.Equal(t, atomic.LoadUint32(&timeouted), uint32(0))
   100  }
   101  
   102  func TestChannelClose(t *testing.T) {
   103  	beginGs := runtime.NumGoroutine()
   104  	ch := New()
   105  	afterGs := runtime.NumGoroutine()
   106  	assert.Equal(t, 1, afterGs-beginGs)
   107  	var exit int32
   108  	go func() {
   109  		for v := range ch.Output() {
   110  			id := v.(int)
   111  			tlogf(t, "consumer=%d started", id)
   112  		}
   113  		atomic.AddInt32(&exit, 1)
   114  	}()
   115  	for i := 1; i <= 20; i++ {
   116  		ch.Input(i)
   117  		tlogf(t, "producer=%d started", i)
   118  	}
   119  	ch.Close()
   120  	for runtime.NumGoroutine() > beginGs {
   121  		tlogf(t, "num goroutines: %d, beginGs: %d", runtime.NumGoroutine(), beginGs)
   122  		runtime.Gosched()
   123  	}
   124  	<-ch.Output() // never block
   125  	assert.Equal(t, int32(1), atomic.LoadInt32(&exit))
   126  }
   127  
   128  func TestChannelGCClose(t *testing.T) {
   129  	beginGs := runtime.NumGoroutine()
   130  	// close implicitly
   131  	go func() {
   132  		_ = New()
   133  	}()
   134  	go func() {
   135  		ch := New()
   136  		ch.Input(1)
   137  		_ = <-ch.Output()
   138  		tlogf(t, "channel finished")
   139  	}()
   140  	for i := 0; i < 3; i++ {
   141  		time.Sleep(time.Millisecond * 10)
   142  		runtime.GC()
   143  	}
   144  	// close explicitly
   145  	go func() {
   146  		ch := New()
   147  		ch.Close()
   148  	}()
   149  	for i := 0; i < 3; i++ {
   150  		time.Sleep(time.Millisecond * 10)
   151  		runtime.GC()
   152  	}
   153  	afterGs := runtime.NumGoroutine()
   154  	assert.Equal(t, beginGs, afterGs)
   155  }
   156  
   157  func TestChannelTimeout(t *testing.T) {
   158  	ch := New(
   159  		WithTimeout(time.Millisecond*50),
   160  		WithSize(1024),
   161  	)
   162  	defer ch.Close()
   163  
   164  	go func() {
   165  		for i := 1; i <= 20; i++ {
   166  			ch.Input(i)
   167  		}
   168  	}()
   169  	var total int32
   170  	go func() {
   171  		for c := range ch.Output() {
   172  			id := c.(int)
   173  			if id >= 10 {
   174  				time.Sleep(time.Millisecond * 100)
   175  			}
   176  			atomic.AddInt32(&total, 1)
   177  		}
   178  	}()
   179  	time.Sleep(time.Second)
   180  	// success task: id in [1, 11]
   181  	// note that task with id=11 also will be consumed since it already checked.
   182  	assert.Equal(t, int32(11), atomic.LoadInt32(&total))
   183  }
   184  
   185  func TestChannelConsumerInflightLimit(t *testing.T) {
   186  	var inflight int32
   187  	var limit int32 = 10
   188  	var total = 20
   189  	ch := New(
   190  		WithThrottle(nil, func(c Channel) bool {
   191  			return atomic.LoadInt32(&inflight) >= limit
   192  		}),
   193  	)
   194  	defer ch.Close()
   195  
   196  	var wg sync.WaitGroup
   197  	go func() {
   198  		for c := range ch.Output() {
   199  			atomic.AddInt32(&inflight, 1)
   200  			id := c.(int)
   201  			tlogf(t, "consumer=%d started", id)
   202  			go func() {
   203  				defer atomic.AddInt32(&inflight, -1)
   204  				defer wg.Done()
   205  				time.Sleep(time.Second)
   206  				tlogf(t, "consumer=%d finished", id)
   207  			}()
   208  		}
   209  	}()
   210  
   211  	now := time.Now()
   212  	for i := 1; i <= total; i++ {
   213  		wg.Add(1)
   214  		id := i
   215  		ch.Input(id)
   216  		tlogf(t, "producer=%d finished", id)
   217  		time.Sleep(time.Millisecond * 10)
   218  	}
   219  	wg.Wait()
   220  	duration := time.Now().Sub(now)
   221  	assert.Equal(t, 2, int(duration.Seconds()))
   222  }
   223  
   224  func TestChannelProducerSpeedLimit(t *testing.T) {
   225  	var total = 15
   226  	ch := New(WithSize(0))
   227  	defer ch.Close()
   228  
   229  	go func() {
   230  		for c := range ch.Output() {
   231  			id := c.(int)
   232  			time.Sleep(time.Millisecond * 100)
   233  			tlogf(t, "consumer=%d finished", id)
   234  		}
   235  	}()
   236  
   237  	now := time.Now()
   238  	for i := 1; i <= total; i++ {
   239  		id := i
   240  		ch.Input(id)
   241  		tlogf(t, "producer=%d finished", id)
   242  	}
   243  	duration := time.Now().Sub(now)
   244  	assert.Equal(t, 1, int(duration.Seconds()))
   245  }
   246  
   247  func TestChannelProducerNoLimit(t *testing.T) {
   248  	var total = 100
   249  	ch := New(WithSize(1000))
   250  	defer ch.Close()
   251  
   252  	go func() {
   253  		for c := range ch.Output() {
   254  			id := c.(int)
   255  			time.Sleep(time.Millisecond * 100)
   256  			tlogf(t, "consumer=%d finished", id)
   257  		}
   258  	}()
   259  
   260  	now := time.Now()
   261  	for i := 1; i <= total; i++ {
   262  		id := i
   263  		ch.Input(id)
   264  	}
   265  	duration := time.Now().Sub(now)
   266  	assert.Equal(t, 0, int(duration.Seconds()))
   267  }
   268  
   269  func TestChannelGoroutinesThrottle(t *testing.T) {
   270  	goroutineChecker := func(maxGoroutines int) Throttle {
   271  		return func(c Channel) bool {
   272  			tlogf(t, "%d goroutines", runtime.NumGoroutine())
   273  			return runtime.NumGoroutine() > maxGoroutines
   274  		}
   275  	}
   276  	var total = 1000
   277  	throttle := goroutineChecker(100)
   278  	ch := New(WithThrottle(throttle, throttle), WithThrottleWindow(time.Millisecond*100))
   279  	var wg sync.WaitGroup
   280  	go func() {
   281  		for c := range ch.Output() {
   282  			id := c.(int)
   283  			go func() {
   284  				time.Sleep(time.Millisecond * 100)
   285  				tlogf(t, "consumer=%d finished", id)
   286  				wg.Done()
   287  			}()
   288  		}
   289  	}()
   290  
   291  	for i := 1; i <= total; i++ {
   292  		wg.Add(1)
   293  		id := i
   294  		ch.Input(id)
   295  		tlogf(t, "producer=%d finished", id)
   296  		runtime.Gosched()
   297  	}
   298  	wg.Wait()
   299  }
   300  
   301  func TestChannelNoConsumer(t *testing.T) {
   302  	// zero size channel
   303  	ch := New()
   304  	var sum int32
   305  	go func() {
   306  		for i := 1; i <= 20; i++ {
   307  			ch.Input(i)
   308  			tlogf(t, "producer=%d finished", i)
   309  			atomic.AddInt32(&sum, 1)
   310  		}
   311  	}()
   312  	time.Sleep(time.Millisecond * 100)
   313  	assert.Equal(t, int32(2), atomic.LoadInt32(&sum))
   314  
   315  	// 1 size channel
   316  	ch = New(WithSize(1))
   317  	atomic.StoreInt32(&sum, 0)
   318  	go func() {
   319  		for i := 1; i <= 20; i++ {
   320  			ch.Input(i)
   321  			tlogf(t, "producer=%d finished", i)
   322  			atomic.AddInt32(&sum, 1)
   323  		}
   324  	}()
   325  	time.Sleep(time.Millisecond * 100)
   326  	assert.Equal(t, int32(2), atomic.LoadInt32(&sum))
   327  
   328  	// 10 size channel
   329  	ch = New(WithSize(10))
   330  	atomic.StoreInt32(&sum, 0)
   331  	go func() {
   332  		for i := 1; i <= 20; i++ {
   333  			ch.Input(i)
   334  			tlogf(t, "producer=%d finished", i)
   335  			atomic.AddInt32(&sum, 1)
   336  		}
   337  	}()
   338  	time.Sleep(time.Millisecond * 100)
   339  	assert.Equal(t, int32(11), atomic.LoadInt32(&sum))
   340  }
   341  
   342  func TestChannelOneSlowTask(t *testing.T) {
   343  	ch := New(WithTimeout(time.Millisecond*100), WithSize(20))
   344  	defer ch.Close()
   345  
   346  	var total int32
   347  	go func() {
   348  		for c := range ch.Output() {
   349  			id := c.(int)
   350  			if id == 10 {
   351  				time.Sleep(time.Millisecond * 200)
   352  			}
   353  			atomic.AddInt32(&total, 1)
   354  			tlogf(t, "consumer=%d finished", id)
   355  		}
   356  	}()
   357  
   358  	for i := 1; i <= 20; i++ {
   359  		ch.Input(i)
   360  		tlogf(t, "producer=%d finished", i)
   361  	}
   362  	time.Sleep(time.Millisecond * 300)
   363  	assert.Equal(t, int32(11), atomic.LoadInt32(&total))
   364  }
   365  
   366  func TestChannelProduceRateControl(t *testing.T) {
   367  	produceMaxRate := 100
   368  	ch := New(
   369  		WithRateThrottle(produceMaxRate, 0),
   370  	)
   371  	defer ch.Close()
   372  
   373  	go func() {
   374  		for c := range ch.Output() {
   375  			id := c.(int)
   376  			tlogf(t, "consumed: %d", id)
   377  		}
   378  	}()
   379  	begin := time.Now()
   380  	for i := 1; i <= 500; i++ {
   381  		ch.Input(i)
   382  	}
   383  	cost := time.Now().Sub(begin)
   384  	tlogf(t, "Cost %dms", cost.Milliseconds())
   385  }
   386  
   387  func TestChannelConsumeRateControl(t *testing.T) {
   388  	ch := New(
   389  		WithRateThrottle(0, 100),
   390  	)
   391  	defer ch.Close()
   392  
   393  	go func() {
   394  		for c := range ch.Output() {
   395  			id := c.(int)
   396  			tlogf(t, "consumed: %d", id)
   397  		}
   398  	}()
   399  	begin := time.Now()
   400  	for i := 1; i <= 500; i++ {
   401  		ch.Input(i)
   402  	}
   403  	cost := time.Now().Sub(begin)
   404  	tlogf(t, "Cost %dms", cost.Milliseconds())
   405  }
   406  
   407  func TestChannelNonBlock(t *testing.T) {
   408  	ch := New(WithNonBlock())
   409  	defer ch.Close()
   410  
   411  	begin := time.Now()
   412  	for i := 1; i <= 10000; i++ {
   413  		ch.Input(i)
   414  		tlogf(t, "producer=%d finished", i)
   415  	}
   416  	cost := time.Now().Sub(begin)
   417  	tlogf(t, "Cost %dms", cost.Milliseconds())
   418  }
   419  
   420  func TestFastRecoverConsumer(t *testing.T) {
   421  	var consumed int32
   422  	var aborted int32
   423  	timeout := time.Second * 1
   424  	ch := New(
   425  		WithNonBlock(),
   426  		WithTimeout(timeout),
   427  		WithTimeoutCallback(func(i interface{}) {
   428  			atomic.AddInt32(&aborted, 1)
   429  		}),
   430  	)
   431  	defer ch.Close()
   432  
   433  	// consumer
   434  	go func() {
   435  		for c := range ch.Output() {
   436  			id := c.(int)
   437  			t.Logf("consumed: %d", id)
   438  			time.Sleep(time.Millisecond * 100)
   439  			atomic.AddInt32(&consumed, 1)
   440  		}
   441  	}()
   442  
   443  	// producer
   444  	// faster than consumer's ability
   445  	for i := 1; i <= 20; i++ {
   446  		ch.Input(i)
   447  		time.Sleep(time.Millisecond * 10)
   448  	}
   449  	for (atomic.LoadInt32(&consumed) + atomic.LoadInt32(&aborted)) != 20 {
   450  		runtime.Gosched()
   451  	}
   452  	assert.True(t, aborted > 5)
   453  	consumed = 0
   454  	aborted = 0
   455  	// quick recover consumer
   456  	for i := 1; i <= 10; i++ {
   457  		ch.Input(i)
   458  		time.Sleep(time.Millisecond * 10)
   459  	}
   460  	for atomic.LoadInt32(&consumed) != 10 {
   461  		runtime.Gosched()
   462  	}
   463  	// all consumed
   464  }
   465  
   466  func TestChannelCloseThenConsume(t *testing.T) {
   467  	size := 10
   468  	ch := New(WithNonBlock(), WithSize(size))
   469  	for i := 0; i < size; i++ {
   470  		ch.Input(i)
   471  	}
   472  	ch.Close()
   473  	for i := 0; i < size; i++ {
   474  		x := <-ch.Output()
   475  		assert.NotNil(t, x)
   476  		n := x.(int)
   477  		assert.Equal(t, n, x)
   478  	}
   479  }
   480  
   481  func TestChannelInputAndClose(t *testing.T) {
   482  	ch := New(WithSize(1))
   483  	go func() {
   484  		time.Sleep(time.Millisecond * 100)
   485  		ch.Close()
   486  	}()
   487  	begin := time.Now()
   488  	for i := 0; i < 10; i++ {
   489  		ch.Input(1)
   490  	}
   491  	cost := time.Now().Sub(begin)
   492  	assert.True(t, cost.Milliseconds() >= 100)
   493  }