google.golang.org/grpc@v1.62.1/internal/grpcsync/callback_serializer_test.go (about) 1 /* 2 * 3 * Copyright 2022 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package grpcsync 20 21 import ( 22 "context" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 ) 29 30 const ( 31 defaultTestTimeout = 5 * time.Second 32 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 33 ) 34 35 // TestCallbackSerializer_Schedule_FIFO verifies that callbacks are executed in 36 // the same order in which they were scheduled. 37 func (s) TestCallbackSerializer_Schedule_FIFO(t *testing.T) { 38 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 39 cs := NewCallbackSerializer(ctx) 40 defer cancel() 41 42 // We have two channels, one to record the order of scheduling, and the 43 // other to record the order of execution. We spawn a bunch of goroutines 44 // which record the order of scheduling and call the actual Schedule() 45 // method as well. The callbacks record the order of execution. 46 // 47 // We need to grab a lock to record order of scheduling to guarantee that 48 // the act of recording and the act of calling Schedule() happen atomically. 49 const numCallbacks = 100 50 var mu sync.Mutex 51 scheduleOrderCh := make(chan int, numCallbacks) 52 executionOrderCh := make(chan int, numCallbacks) 53 for i := 0; i < numCallbacks; i++ { 54 go func(id int) { 55 mu.Lock() 56 defer mu.Unlock() 57 scheduleOrderCh <- id 58 cs.Schedule(func(ctx context.Context) { 59 select { 60 case <-ctx.Done(): 61 return 62 case executionOrderCh <- id: 63 } 64 }) 65 }(i) 66 } 67 68 // Spawn a couple of goroutines to capture the order or scheduling and the 69 // order of execution. 70 scheduleOrder := make([]int, numCallbacks) 71 executionOrder := make([]int, numCallbacks) 72 var wg sync.WaitGroup 73 wg.Add(2) 74 go func() { 75 defer wg.Done() 76 for i := 0; i < numCallbacks; i++ { 77 select { 78 case <-ctx.Done(): 79 return 80 case id := <-scheduleOrderCh: 81 scheduleOrder[i] = id 82 } 83 } 84 }() 85 go func() { 86 defer wg.Done() 87 for i := 0; i < numCallbacks; i++ { 88 select { 89 case <-ctx.Done(): 90 return 91 case id := <-executionOrderCh: 92 executionOrder[i] = id 93 } 94 } 95 }() 96 wg.Wait() 97 98 if diff := cmp.Diff(executionOrder, scheduleOrder); diff != "" { 99 t.Fatalf("Callbacks are not executed in scheduled order. diff(-want, +got):\n%s", diff) 100 } 101 } 102 103 // TestCallbackSerializer_Schedule_Concurrent verifies that all concurrently 104 // scheduled callbacks get executed. 105 func (s) TestCallbackSerializer_Schedule_Concurrent(t *testing.T) { 106 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 107 cs := NewCallbackSerializer(ctx) 108 defer cancel() 109 110 // Schedule callbacks concurrently by calling Schedule() from goroutines. 111 // The execution of the callbacks call Done() on the waitgroup, which 112 // eventually unblocks the test and allows it to complete. 113 const numCallbacks = 100 114 var wg sync.WaitGroup 115 wg.Add(numCallbacks) 116 for i := 0; i < numCallbacks; i++ { 117 go func() { 118 cs.Schedule(func(context.Context) { 119 wg.Done() 120 }) 121 }() 122 } 123 124 // We call Wait() on the waitgroup from a goroutine so that we can select on 125 // the Wait() being unblocked and the overall test deadline expiring. 126 done := make(chan struct{}) 127 go func() { 128 wg.Wait() 129 close(done) 130 }() 131 132 select { 133 case <-ctx.Done(): 134 t.Fatal("Timeout waiting for all scheduled callbacks to be executed") 135 case <-done: 136 } 137 } 138 139 // TestCallbackSerializer_Schedule_Close verifies that callbacks in the queue 140 // are not executed once Close() returns. 141 func (s) TestCallbackSerializer_Schedule_Close(t *testing.T) { 142 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 143 defer cancel() 144 145 serializerCtx, serializerCancel := context.WithTimeout(context.Background(), defaultTestTimeout) 146 cs := NewCallbackSerializer(serializerCtx) 147 148 // Schedule a callback which blocks until the context passed to it is 149 // canceled. It also closes a channel to signal that it has started. 150 firstCallbackStartedCh := make(chan struct{}) 151 cs.Schedule(func(ctx context.Context) { 152 close(firstCallbackStartedCh) 153 <-ctx.Done() 154 }) 155 156 // Schedule a bunch of callbacks. These should be exeuted since the are 157 // scheduled before the serializer is closed. 158 const numCallbacks = 10 159 callbackCh := make(chan int, numCallbacks) 160 for i := 0; i < numCallbacks; i++ { 161 num := i 162 if !cs.Schedule(func(context.Context) { callbackCh <- num }) { 163 t.Fatal("Schedule failed to accept a callback when the serializer is yet to be closed") 164 } 165 } 166 167 // Ensure that none of the newer callbacks are executed at this point. 168 select { 169 case <-time.After(defaultTestShortTimeout): 170 case <-callbackCh: 171 t.Fatal("Newer callback executed when older one is still executing") 172 } 173 174 // Wait for the first callback to start before closing the scheduler. 175 <-firstCallbackStartedCh 176 177 // Cancel the context which will unblock the first callback. All of the 178 // other callbacks (which have not started executing at this point) should 179 // be executed after this. 180 serializerCancel() 181 182 // Ensure that the newer callbacks are executed. 183 for i := 0; i < numCallbacks; i++ { 184 select { 185 case <-ctx.Done(): 186 t.Fatal("Timeout when waiting for callback scheduled before close to be executed") 187 case num := <-callbackCh: 188 if num != i { 189 t.Fatalf("Executing callback %d, want %d", num, i) 190 } 191 } 192 } 193 <-cs.Done() 194 195 done := make(chan struct{}) 196 if cs.Schedule(func(context.Context) { close(done) }) { 197 t.Fatal("Scheduled a callback after closing the serializer") 198 } 199 200 // Ensure that the lates callback is executed at this point. 201 select { 202 case <-time.After(defaultTestShortTimeout): 203 case <-done: 204 t.Fatal("Newer callback executed when scheduled after closing serializer") 205 } 206 }