github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/app/stats/channel_test.go (about) 1 package stats_test 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 . "github.com/xtls/xray-core/app/stats" 10 "github.com/xtls/xray-core/common" 11 "github.com/xtls/xray-core/features/stats" 12 ) 13 14 func TestStatsChannel(t *testing.T) { 15 // At most 2 subscribers could be registered 16 c := NewChannel(&ChannelConfig{SubscriberLimit: 2, Blocking: true}) 17 18 a, err := stats.SubscribeRunnableChannel(c) 19 common.Must(err) 20 if !c.Running() { 21 t.Fatal("unexpected failure in running channel after first subscription") 22 } 23 24 b, err := c.Subscribe() 25 common.Must(err) 26 27 // Test that third subscriber is forbidden 28 _, err = c.Subscribe() 29 if err == nil { 30 t.Fatal("unexpected successful subscription") 31 } 32 t.Log("expected error: ", err) 33 34 stopCh := make(chan struct{}) 35 errCh := make(chan string) 36 37 go func() { 38 c.Publish(context.Background(), 1) 39 c.Publish(context.Background(), 2) 40 c.Publish(context.Background(), "3") 41 c.Publish(context.Background(), []int{4}) 42 stopCh <- struct{}{} 43 }() 44 45 go func() { 46 if v, ok := (<-a).(int); !ok || v != 1 { 47 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) 48 } 49 if v, ok := (<-a).(int); !ok || v != 2 { 50 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) 51 } 52 if v, ok := (<-a).(string); !ok || v != "3" { 53 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") 54 } 55 if v, ok := (<-a).([]int); !ok || v[0] != 4 { 56 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) 57 } 58 stopCh <- struct{}{} 59 }() 60 61 go func() { 62 if v, ok := (<-b).(int); !ok || v != 1 { 63 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) 64 } 65 if v, ok := (<-b).(int); !ok || v != 2 { 66 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) 67 } 68 if v, ok := (<-b).(string); !ok || v != "3" { 69 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", "3") 70 } 71 if v, ok := (<-b).([]int); !ok || v[0] != 4 { 72 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", []int{4}) 73 } 74 stopCh <- struct{}{} 75 }() 76 77 timeout := time.After(2 * time.Second) 78 for i := 0; i < 3; i++ { 79 select { 80 case <-timeout: 81 t.Fatal("Test timeout after 2s") 82 case e := <-errCh: 83 t.Fatal(e) 84 case <-stopCh: 85 } 86 } 87 88 // Test the unsubscription of channel 89 common.Must(c.Unsubscribe(b)) 90 91 // Test the last subscriber will close channel with `UnsubscribeClosableChannel` 92 common.Must(stats.UnsubscribeClosableChannel(c, a)) 93 if c.Running() { 94 t.Fatal("unexpected running channel after unsubscribing the last subscriber") 95 } 96 } 97 98 func TestStatsChannelUnsubcribe(t *testing.T) { 99 c := NewChannel(&ChannelConfig{Blocking: true}) 100 common.Must(c.Start()) 101 defer c.Close() 102 103 a, err := c.Subscribe() 104 common.Must(err) 105 defer c.Unsubscribe(a) 106 107 b, err := c.Subscribe() 108 common.Must(err) 109 110 pauseCh := make(chan struct{}) 111 stopCh := make(chan struct{}) 112 errCh := make(chan string) 113 114 { 115 var aSet, bSet bool 116 for _, s := range c.Subscribers() { 117 if s == a { 118 aSet = true 119 } 120 if s == b { 121 bSet = true 122 } 123 } 124 if !(aSet && bSet) { 125 t.Fatal("unexpected subscribers: ", c.Subscribers()) 126 } 127 } 128 129 go func() { // Blocking publish 130 c.Publish(context.Background(), 1) 131 <-pauseCh // Wait for `b` goroutine to resume sending message 132 c.Publish(context.Background(), 2) 133 }() 134 135 go func() { 136 if v, ok := (<-a).(int); !ok || v != 1 { 137 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) 138 } 139 if v, ok := (<-a).(int); !ok || v != 2 { 140 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) 141 } 142 }() 143 144 go func() { 145 if v, ok := (<-b).(int); !ok || v != 1 { 146 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) 147 } 148 // Unsubscribe `b` while publishing is paused 149 c.Unsubscribe(b) 150 { // Test `b` is not in subscribers 151 var aSet, bSet bool 152 for _, s := range c.Subscribers() { 153 if s == a { 154 aSet = true 155 } 156 if s == b { 157 bSet = true 158 } 159 } 160 if !(aSet && !bSet) { 161 errCh <- fmt.Sprint("unexpected subscribers: ", c.Subscribers()) 162 } 163 } 164 // Resume publishing progress 165 close(pauseCh) 166 // Test `b` is neither closed nor able to receive any data 167 select { 168 case v, ok := <-b: 169 if ok { 170 errCh <- fmt.Sprint("unexpected data received: ", v) 171 } else { 172 errCh <- fmt.Sprint("unexpected closed channel: ", b) 173 } 174 default: 175 } 176 close(stopCh) 177 }() 178 179 select { 180 case <-time.After(2 * time.Second): 181 t.Fatal("Test timeout after 2s") 182 case e := <-errCh: 183 t.Fatal(e) 184 case <-stopCh: 185 } 186 } 187 188 func TestStatsChannelBlocking(t *testing.T) { 189 // Do not use buffer so as to create blocking scenario 190 c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) 191 common.Must(c.Start()) 192 defer c.Close() 193 194 a, err := c.Subscribe() 195 common.Must(err) 196 defer c.Unsubscribe(a) 197 198 pauseCh := make(chan struct{}) 199 stopCh := make(chan struct{}) 200 errCh := make(chan string) 201 202 ctx, cancel := context.WithCancel(context.Background()) 203 204 // Test blocking channel publishing 205 go func() { 206 // Dummy messsage with no subscriber receiving, will block broadcasting goroutine 207 c.Publish(context.Background(), nil) 208 209 <-pauseCh 210 211 // Publishing should be blocked here, for last message was not cleared and buffer was full 212 c.Publish(context.Background(), nil) 213 214 pauseCh <- struct{}{} 215 216 // Publishing should still be blocked here 217 c.Publish(ctx, nil) 218 219 // Check publishing is done because context is canceled 220 select { 221 case <-ctx.Done(): 222 if ctx.Err() != context.Canceled { 223 errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) 224 } 225 default: 226 errCh <- "unexpected non-blocked publishing" 227 } 228 close(stopCh) 229 }() 230 231 go func() { 232 pauseCh <- struct{}{} 233 234 select { 235 case <-pauseCh: 236 errCh <- "unexpected non-blocked publishing" 237 case <-time.After(100 * time.Millisecond): 238 } 239 240 // Receive first published message 241 <-a 242 243 select { 244 case <-pauseCh: 245 case <-time.After(100 * time.Millisecond): 246 errCh <- "unexpected blocking publishing" 247 } 248 249 // Manually cancel the context to end publishing 250 cancel() 251 }() 252 253 select { 254 case <-time.After(2 * time.Second): 255 t.Fatal("Test timeout after 2s") 256 case e := <-errCh: 257 t.Fatal(e) 258 case <-stopCh: 259 } 260 } 261 262 func TestStatsChannelNonBlocking(t *testing.T) { 263 // Do not use buffer so as to create blocking scenario 264 c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: false}) 265 common.Must(c.Start()) 266 defer c.Close() 267 268 a, err := c.Subscribe() 269 common.Must(err) 270 defer c.Unsubscribe(a) 271 272 pauseCh := make(chan struct{}) 273 stopCh := make(chan struct{}) 274 errCh := make(chan string) 275 276 ctx, cancel := context.WithCancel(context.Background()) 277 278 // Test blocking channel publishing 279 go func() { 280 c.Publish(context.Background(), nil) 281 c.Publish(context.Background(), nil) 282 pauseCh <- struct{}{} 283 <-pauseCh 284 c.Publish(ctx, nil) 285 c.Publish(ctx, nil) 286 // Check publishing is done because context is canceled 287 select { 288 case <-ctx.Done(): 289 if ctx.Err() != context.Canceled { 290 errCh <- fmt.Sprint("unexpected error: ", ctx.Err()) 291 } 292 case <-time.After(100 * time.Millisecond): 293 errCh <- "unexpected non-cancelled publishing" 294 } 295 }() 296 297 go func() { 298 // Check publishing won't block even if there is no subscriber receiving message 299 select { 300 case <-pauseCh: 301 case <-time.After(100 * time.Millisecond): 302 errCh <- "unexpected blocking publishing" 303 } 304 305 // Receive first and second published message 306 <-a 307 <-a 308 309 pauseCh <- struct{}{} 310 311 // Manually cancel the context to end publishing 312 cancel() 313 314 // Check third and forth published message is cancelled and cannot receive 315 <-time.After(100 * time.Millisecond) 316 select { 317 case <-a: 318 errCh <- "unexpected non-cancelled publishing" 319 default: 320 } 321 select { 322 case <-a: 323 errCh <- "unexpected non-cancelled publishing" 324 default: 325 } 326 close(stopCh) 327 }() 328 329 select { 330 case <-time.After(2 * time.Second): 331 t.Fatal("Test timeout after 2s") 332 case e := <-errCh: 333 t.Fatal(e) 334 case <-stopCh: 335 } 336 } 337 338 func TestStatsChannelConcurrency(t *testing.T) { 339 // Do not use buffer so as to create blocking scenario 340 c := NewChannel(&ChannelConfig{BufferSize: 0, Blocking: true}) 341 common.Must(c.Start()) 342 defer c.Close() 343 344 a, err := c.Subscribe() 345 common.Must(err) 346 defer c.Unsubscribe(a) 347 348 b, err := c.Subscribe() 349 common.Must(err) 350 defer c.Unsubscribe(b) 351 352 stopCh := make(chan struct{}) 353 errCh := make(chan string) 354 355 go func() { // Blocking publish 356 c.Publish(context.Background(), 1) 357 c.Publish(context.Background(), 2) 358 }() 359 360 go func() { 361 if v, ok := (<-a).(int); !ok || v != 1 { 362 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 1) 363 } 364 if v, ok := (<-a).(int); !ok || v != 2 { 365 errCh <- fmt.Sprint("unexpected receiving: ", v, ", wanted ", 2) 366 } 367 }() 368 369 go func() { 370 // Block `b` for a time so as to ensure source channel is trying to send message to `b`. 371 <-time.After(25 * time.Millisecond) 372 // This causes concurrency scenario: unsubscribe `b` while trying to send message to it 373 c.Unsubscribe(b) 374 // Test `b` is not closed and can still receive data 1: 375 // Because unsubscribe won't affect the ongoing process of sending message. 376 select { 377 case v, ok := <-b: 378 if v1, ok1 := v.(int); !(ok && ok1 && v1 == 1) { 379 errCh <- fmt.Sprint("unexpected failure in receiving data: ", 1) 380 } 381 default: 382 errCh <- fmt.Sprint("unexpected block from receiving data: ", 1) 383 } 384 // Test `b` is not closed but cannot receive data 2: 385 // Because in a new round of messaging, `b` has been unsubscribed. 386 select { 387 case v, ok := <-b: 388 if ok { 389 errCh <- fmt.Sprint("unexpected receiving: ", v) 390 } else { 391 errCh <- "unexpected closing of channel" 392 } 393 default: 394 } 395 close(stopCh) 396 }() 397 398 select { 399 case <-time.After(2 * time.Second): 400 t.Fatal("Test timeout after 2s") 401 case e := <-errCh: 402 t.Fatal(e) 403 case <-stopCh: 404 } 405 }