google.golang.org/grpc@v1.62.1/internal/cache/timeoutCache_test.go (about) 1 /* 2 * 3 * Copyright 2019 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 package cache 19 20 import ( 21 "strconv" 22 "sync" 23 "testing" 24 "time" 25 26 "google.golang.org/grpc/internal/grpctest" 27 ) 28 29 const ( 30 testCacheTimeout = 100 * time.Millisecond 31 ) 32 33 type s struct { 34 grpctest.Tester 35 } 36 37 func Test(t *testing.T) { 38 grpctest.RunSubTests(t, s{}) 39 } 40 41 func (c *TimeoutCache) getForTesting(key any) (*cacheEntry, bool) { 42 c.mu.Lock() 43 defer c.mu.Unlock() 44 r, ok := c.cache[key] 45 return r, ok 46 } 47 48 // TestCacheExpire attempts to add an entry to the cache and verifies that it 49 // was added successfully. It then makes sure that on timeout, it's removed and 50 // the associated callback is called. 51 func (s) TestCacheExpire(t *testing.T) { 52 const k, v = 1, "1" 53 c := NewTimeoutCache(testCacheTimeout) 54 55 callbackChan := make(chan struct{}) 56 c.Add(k, v, func() { close(callbackChan) }) 57 58 if gotV, ok := c.getForTesting(k); !ok || gotV.item != v { 59 t.Fatalf("After Add(), before timeout, from cache got: %v, %v, want %v, %v", gotV.item, ok, v, true) 60 } 61 if l := c.Len(); l != 1 { 62 t.Fatalf("%d number of items in the cache, want 1", l) 63 } 64 65 select { 66 case <-callbackChan: 67 case <-time.After(testCacheTimeout * 2): 68 t.Fatalf("timeout waiting for callback") 69 } 70 71 if _, ok := c.getForTesting(k); ok { 72 t.Fatalf("After Add(), after timeout, from cache got: _, %v, want _, %v", ok, false) 73 } 74 if l := c.Len(); l != 0 { 75 t.Fatalf("%d number of items in the cache, want 0", l) 76 } 77 } 78 79 // TestCacheRemove attempts to remove an existing entry from the cache and 80 // verifies that the entry is removed and the associated callback is not 81 // invoked. 82 func (s) TestCacheRemove(t *testing.T) { 83 const k, v = 1, "1" 84 c := NewTimeoutCache(testCacheTimeout) 85 86 callbackChan := make(chan struct{}) 87 c.Add(k, v, func() { close(callbackChan) }) 88 89 if got, ok := c.getForTesting(k); !ok || got.item != v { 90 t.Fatalf("After Add(), before timeout, from cache got: %v, %v, want %v, %v", got.item, ok, v, true) 91 } 92 if l := c.Len(); l != 1 { 93 t.Fatalf("%d number of items in the cache, want 1", l) 94 } 95 96 time.Sleep(testCacheTimeout / 2) 97 98 gotV, gotOK := c.Remove(k) 99 if !gotOK || gotV != v { 100 t.Fatalf("After Add(), before timeout, Remove() got: %v, %v, want %v, %v", gotV, gotOK, v, true) 101 } 102 103 if _, ok := c.getForTesting(k); ok { 104 t.Fatalf("After Add(), before timeout, after Remove(), from cache got: _, %v, want _, %v", ok, false) 105 } 106 if l := c.Len(); l != 0 { 107 t.Fatalf("%d number of items in the cache, want 0", l) 108 } 109 110 select { 111 case <-callbackChan: 112 t.Fatalf("unexpected callback after retrieve") 113 case <-time.After(testCacheTimeout * 2): 114 } 115 } 116 117 // TestCacheClearWithoutCallback attempts to clear all entries from the cache 118 // and verifies that the associated callbacks are not invoked. 119 func (s) TestCacheClearWithoutCallback(t *testing.T) { 120 var values []string 121 const itemCount = 3 122 for i := 0; i < itemCount; i++ { 123 values = append(values, strconv.Itoa(i)) 124 } 125 c := NewTimeoutCache(testCacheTimeout) 126 127 done := make(chan struct{}) 128 defer close(done) 129 callbackChan := make(chan struct{}, itemCount) 130 131 for i, v := range values { 132 callbackChanTemp := make(chan struct{}) 133 c.Add(i, v, func() { close(callbackChanTemp) }) 134 go func() { 135 select { 136 case <-callbackChanTemp: 137 callbackChan <- struct{}{} 138 case <-done: 139 } 140 }() 141 } 142 143 for i, v := range values { 144 if got, ok := c.getForTesting(i); !ok || got.item != v { 145 t.Fatalf("After Add(), before timeout, from cache got: %v, %v, want %v, %v", got.item, ok, v, true) 146 } 147 } 148 if l := c.Len(); l != itemCount { 149 t.Fatalf("%d number of items in the cache, want %d", l, itemCount) 150 } 151 152 time.Sleep(testCacheTimeout / 2) 153 c.Clear(false) 154 155 for i := range values { 156 if _, ok := c.getForTesting(i); ok { 157 t.Fatalf("After Add(), before timeout, after Remove(), from cache got: _, %v, want _, %v", ok, false) 158 } 159 } 160 if l := c.Len(); l != 0 { 161 t.Fatalf("%d number of items in the cache, want 0", l) 162 } 163 164 select { 165 case <-callbackChan: 166 t.Fatalf("unexpected callback after Clear") 167 case <-time.After(testCacheTimeout * 2): 168 } 169 } 170 171 // TestCacheClearWithCallback attempts to clear all entries from the cache and 172 // verifies that the associated callbacks are invoked. 173 func (s) TestCacheClearWithCallback(t *testing.T) { 174 var values []string 175 const itemCount = 3 176 for i := 0; i < itemCount; i++ { 177 values = append(values, strconv.Itoa(i)) 178 } 179 c := NewTimeoutCache(time.Hour) 180 181 testDone := make(chan struct{}) 182 defer close(testDone) 183 184 var wg sync.WaitGroup 185 wg.Add(itemCount) 186 for i, v := range values { 187 callbackChanTemp := make(chan struct{}) 188 c.Add(i, v, func() { close(callbackChanTemp) }) 189 go func() { 190 defer wg.Done() 191 select { 192 case <-callbackChanTemp: 193 case <-testDone: 194 } 195 }() 196 } 197 198 allGoroutineDone := make(chan struct{}, itemCount) 199 go func() { 200 wg.Wait() 201 close(allGoroutineDone) 202 }() 203 204 for i, v := range values { 205 if got, ok := c.getForTesting(i); !ok || got.item != v { 206 t.Fatalf("After Add(), before timeout, from cache got: %v, %v, want %v, %v", got.item, ok, v, true) 207 } 208 } 209 if l := c.Len(); l != itemCount { 210 t.Fatalf("%d number of items in the cache, want %d", l, itemCount) 211 } 212 213 time.Sleep(testCacheTimeout / 2) 214 c.Clear(true) 215 216 for i := range values { 217 if _, ok := c.getForTesting(i); ok { 218 t.Fatalf("After Add(), before timeout, after Remove(), from cache got: _, %v, want _, %v", ok, false) 219 } 220 } 221 if l := c.Len(); l != 0 { 222 t.Fatalf("%d number of items in the cache, want 0", l) 223 } 224 225 select { 226 case <-allGoroutineDone: 227 case <-time.After(testCacheTimeout * 2): 228 t.Fatalf("timeout waiting for all callbacks") 229 } 230 } 231 232 // TestCacheRetrieveTimeoutRace simulates the case where an entry's timer fires 233 // around the same time that Remove() is called for it. It verifies that there 234 // is no deadlock. 235 func (s) TestCacheRetrieveTimeoutRace(t *testing.T) { 236 c := NewTimeoutCache(time.Nanosecond) 237 238 done := make(chan struct{}) 239 go func() { 240 for i := 0; i < 1000; i++ { 241 // Add starts a timer with 1 ns timeout, then remove will race 242 // with the timer. 243 c.Add(i, strconv.Itoa(i), func() {}) 244 c.Remove(i) 245 } 246 close(done) 247 }() 248 249 select { 250 case <-time.After(time.Second): 251 t.Fatalf("Test didn't finish within 1 second. Deadlock") 252 case <-done: 253 } 254 }