github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/protoproxy_test.go (about)

     1  // Copyright 2023 LiveKit, 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 utils
    16  
    17  import (
    18  	"runtime"
    19  	"sync/atomic"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/stretchr/testify/require"
    24  
    25  	"github.com/livekit/protocol/livekit"
    26  )
    27  
    28  func TestProtoProxy(t *testing.T) {
    29  	t.Run("basics", func(t *testing.T) {
    30  		numGoRoutines := runtime.NumGoroutine()
    31  		proxy, numParticipants, freeze := createTestProxy()
    32  
    33  		select {
    34  		case <-proxy.Updated():
    35  			t.Fatal("should not have received an update")
    36  		default:
    37  		}
    38  
    39  		// should not have changed, initial value should persist
    40  		require.EqualValues(t, 0, proxy.Get().NumParticipants)
    41  
    42  		// immediate change
    43  		proxy.MarkDirty(true)
    44  		time.Sleep(100 * time.Millisecond)
    45  
    46  		require.EqualValues(t, 2, numParticipants.Load())
    47  		require.EqualValues(t, 1, proxy.Get().NumParticipants)
    48  
    49  		// queued updates
    50  		proxy.MarkDirty(false)
    51  		select {
    52  		case <-proxy.Updated():
    53  			// consume previous notification
    54  		default:
    55  		}
    56  		require.EqualValues(t, 1, proxy.Get().NumParticipants)
    57  
    58  		// freeze and ensure that updates are not triggered
    59  		freeze.Store(true)
    60  		// freezing and consuming the previous notification to ensure counter does not increase in updateFn
    61  		select {
    62  		case <-proxy.Updated():
    63  		case <-time.After(100 * time.Millisecond):
    64  			t.Fatal("should have received an update")
    65  		}
    66  		// possible that ticker was updated while markDirty queued another update
    67  		require.GreaterOrEqual(t, int(proxy.Get().NumParticipants), 2)
    68  
    69  		// trigger another update, but should not get notification as freeze is in place and the model should not have changed
    70  		proxy.MarkDirty(false)
    71  		time.Sleep(100 * time.Millisecond)
    72  		select {
    73  		case <-proxy.Updated():
    74  			t.Fatal("should not have received an update")
    75  		default:
    76  		}
    77  		require.EqualValues(t, 2, proxy.Get().NumParticipants)
    78  
    79  		// ensure we didn't leak
    80  		proxy.Stop()
    81  
    82  		for i := 0; i < 10; i++ {
    83  			if runtime.NumGoroutine() <= numGoRoutines {
    84  				break
    85  			}
    86  			time.Sleep(100 * time.Millisecond)
    87  		}
    88  		require.LessOrEqual(t, runtime.NumGoroutine(), numGoRoutines)
    89  	})
    90  
    91  	t.Run("await next update after marking dirty", func(t *testing.T) {
    92  		proxy, _, _ := createTestProxy()
    93  		require.EqualValues(t, 0, proxy.Get().NumParticipants)
    94  		<-proxy.MarkDirty(true)
    95  		require.EqualValues(t, 1, proxy.Get().NumParticipants)
    96  	})
    97  
    98  	t.Run("await resolves when proxy is stopped", func(t *testing.T) {
    99  		proxy, _, _ := createTestProxy()
   100  		done := proxy.MarkDirty(true)
   101  		proxy.Stop()
   102  		<-done
   103  	})
   104  
   105  	t.Run("multiple awaits resolve for one update", func(t *testing.T) {
   106  		proxy, _, _ := createTestProxy()
   107  		done0 := proxy.MarkDirty(false)
   108  		done1 := proxy.MarkDirty(true)
   109  		<-done0
   110  		<-done1
   111  		require.EqualValues(t, 1, proxy.Get().NumParticipants)
   112  	})
   113  
   114  	t.Run("await resolve when there is no change", func(t *testing.T) {
   115  		proxy := NewProtoProxy[*livekit.Room](10*time.Millisecond, func() *livekit.Room { return nil })
   116  		done := proxy.MarkDirty(true)
   117  		time.Sleep(100 * time.Millisecond)
   118  		select {
   119  		case <-done:
   120  		default:
   121  			t.FailNow()
   122  		}
   123  	})
   124  }
   125  
   126  func createTestProxy() (*ProtoProxy[*livekit.Room], *atomic.Uint32, *atomic.Bool) {
   127  	// uses an update func that increments numParticipants each time
   128  	var numParticipants atomic.Uint32
   129  	var freeze atomic.Bool
   130  	return NewProtoProxy[*livekit.Room](
   131  		10*time.Millisecond,
   132  		func() *livekit.Room {
   133  			if !freeze.Load() {
   134  				defer numParticipants.Add(1)
   135  			}
   136  			return &livekit.Room{
   137  				NumParticipants: numParticipants.Load(),
   138  			}
   139  		},
   140  	), &numParticipants, &freeze
   141  }