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 }