github.com/imannamdari/v2ray-core/v5@v5.0.5/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/imannamdari/v2ray-core/v5/app/stats"
    10  	"github.com/imannamdari/v2ray-core/v5/common"
    11  	"github.com/imannamdari/v2ray-core/v5/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 message 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  }