github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/block_retrieval_queue_test.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "io" 9 "testing" 10 "time" 11 12 "github.com/eapache/channels" 13 "github.com/golang/mock/gomock" 14 "github.com/keybase/client/go/kbfs/data" 15 "github.com/keybase/client/go/kbfs/env" 16 "github.com/keybase/client/go/kbfs/kbfsblock" 17 "github.com/keybase/client/go/kbfs/libkey" 18 libkeytest "github.com/keybase/client/go/kbfs/libkey/test" 19 "github.com/keybase/client/go/kbfs/test/clocktest" 20 "github.com/keybase/client/go/kbfs/tlf" 21 "github.com/keybase/client/go/libkb" 22 "github.com/keybase/client/go/protocol/keybase1" 23 "github.com/stretchr/testify/require" 24 "golang.org/x/net/context" 25 ) 26 27 type testBlockRetrievalConfig struct { 28 codecGetter 29 logMaker 30 testCache data.BlockCache 31 bg blockGetter 32 bs BlockServer 33 *testDiskBlockCacheGetter 34 *testSyncedTlfGetterSetter 35 initModeGetter 36 clock Clock 37 reporter Reporter 38 subscriptionManager SubscriptionManager 39 subscriptionManagerPublisher SubscriptionManagerPublisher 40 } 41 42 func newTestBlockRetrievalConfig(t *testing.T, bg blockGetter, 43 dbc DiskBlockCache) *testBlockRetrievalConfig { 44 clock := clocktest.NewTestClockNow() 45 ctlr := gomock.NewController(t) 46 mockPublisher := NewMockSubscriptionManagerPublisher(ctlr) 47 mockPublisher.EXPECT().PublishChange(gomock.Any()).AnyTimes() 48 return &testBlockRetrievalConfig{ 49 newTestCodecGetter(), 50 newTestLogMakerWithVDebug(t, libkb.VLog2String), 51 data.NewBlockCacheStandard(10, getDefaultCleanBlockCacheCapacity(NewInitModeFromType(InitDefault))), 52 bg, 53 NewMockBlockServer(ctlr), 54 newTestDiskBlockCacheGetter(t, dbc), 55 newTestSyncedTlfGetterSetter(), 56 testInitModeGetter{InitDefault}, 57 clock, 58 NewReporterSimple(clock, 1), 59 nil, 60 mockPublisher, 61 } 62 } 63 64 func (c *testBlockRetrievalConfig) BlockCache() data.BlockCache { 65 return c.testCache 66 } 67 68 func (c testBlockRetrievalConfig) DataVersion() data.Ver { 69 return data.ChildHolesVer 70 } 71 72 func (c testBlockRetrievalConfig) Clock() Clock { 73 return c.clock 74 } 75 76 func (c testBlockRetrievalConfig) Reporter() Reporter { 77 return c.reporter 78 } 79 80 func (c testBlockRetrievalConfig) blockGetter() blockGetter { 81 return c.bg 82 } 83 84 func (c testBlockRetrievalConfig) BlockServer() BlockServer { 85 return c.bs 86 } 87 88 func (c testBlockRetrievalConfig) GetSettingsDB() *SettingsDB { 89 return nil 90 } 91 92 func (c testBlockRetrievalConfig) SubscriptionManager( 93 _ SubscriptionManagerClientID, _ bool, 94 _ SubscriptionNotifier) SubscriptionManager { 95 return c.subscriptionManager 96 } 97 98 func (c testBlockRetrievalConfig) SubscriptionManagerPublisher() SubscriptionManagerPublisher { 99 return c.subscriptionManagerPublisher 100 } 101 102 func makeRandomBlockPointer(t *testing.T) data.BlockPointer { 103 id, err := kbfsblock.MakeFakeID() 104 require.NoError(t, err) 105 return data.BlockPointer{ 106 ID: id, 107 KeyGen: 5, 108 DataVer: 1, 109 DirectType: data.DirectBlock, 110 Context: kbfsblock.MakeContext( 111 "fake creator", 112 "fake writer", 113 kbfsblock.RefNonce{0xb}, 114 keybase1.BlockType_DATA, 115 ), 116 } 117 } 118 119 func makeKMD() libkey.KeyMetadata { 120 return libkeytest.NewEmptyKeyMetadata(tlf.FakeID(0, tlf.Private), 1) 121 } 122 123 func initBlockRetrievalQueueTest(t *testing.T) *blockRetrievalQueue { 124 q := newBlockRetrievalQueue( 125 0, 0, 0, newTestBlockRetrievalConfig(t, nil, nil), 126 env.EmptyAppStateUpdater{}) 127 <-q.TogglePrefetcher(false, nil, nil) 128 return q 129 } 130 131 func endBlockRetrievalQueueTest(t *testing.T, q *blockRetrievalQueue) { 132 t.Helper() 133 select { 134 case <-q.Shutdown(): 135 case <-time.After(5 * time.Second): 136 t.Fatal("Waited too long for block retrieval queue to shutdown") 137 } 138 } 139 140 func TestBlockRetrievalQueueBasic(t *testing.T) { 141 t.Log("Add a block retrieval request to the queue and retrieve it.") 142 q := initBlockRetrievalQueueTest(t) 143 require.NotNil(t, q) 144 defer endBlockRetrievalQueueTest(t, q) 145 146 ctx := context.Background() 147 ptr1 := makeRandomBlockPointer(t) 148 block := &data.FileBlock{} 149 t.Log("Request a block retrieval for ptr1.") 150 _ = q.Request( 151 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 152 data.NoCacheEntry, BlockRequestWithPrefetch) 153 154 t.Log("Begin working on the request.") 155 br := q.popIfNotEmpty() 156 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 157 require.Equal(t, ptr1, br.blockPtr) 158 require.Equal(t, -1, br.index) 159 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 160 require.Equal(t, uint64(0), br.insertionOrder) 161 require.Len(t, br.requests, 1) 162 require.Equal(t, block, br.requests[0].block) 163 } 164 165 func TestBlockRetrievalQueuePreemptPriority(t *testing.T) { 166 t.Log("Preempt a lower-priority block retrieval request with a higher " + 167 "priority request.") 168 q := initBlockRetrievalQueueTest(t) 169 require.NotNil(t, q) 170 defer endBlockRetrievalQueueTest(t, q) 171 172 ctx := context.Background() 173 ptr1 := makeRandomBlockPointer(t) 174 ptr2 := makeRandomBlockPointer(t) 175 block := &data.FileBlock{} 176 t.Log("Request a block retrieval for ptr1 and a higher priority " + 177 "retrieval for ptr2.") 178 _ = q.Request( 179 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 180 data.NoCacheEntry, BlockRequestWithPrefetch) 181 _ = q.Request(ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr2, 182 block, data.NoCacheEntry, BlockRequestWithPrefetch) 183 184 t.Log("Begin working on the preempted ptr2 request.") 185 br := q.popIfNotEmpty() 186 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 187 require.Equal(t, ptr2, br.blockPtr) 188 require.Equal(t, defaultOnDemandRequestPriority+1, br.priority) 189 require.Equal(t, uint64(1), br.insertionOrder) 190 191 t.Log("Begin working on the ptr1 request.") 192 br = q.popIfNotEmpty() 193 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 194 require.Equal(t, ptr1, br.blockPtr) 195 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 196 require.Equal(t, uint64(0), br.insertionOrder) 197 } 198 199 func TestBlockRetrievalQueueInterleavedPreemption(t *testing.T) { 200 t.Log("Handle a first request and then preempt another one.") 201 q := initBlockRetrievalQueueTest(t) 202 require.NotNil(t, q) 203 defer endBlockRetrievalQueueTest(t, q) 204 205 ctx := context.Background() 206 ptr1 := makeRandomBlockPointer(t) 207 ptr2 := makeRandomBlockPointer(t) 208 block := &data.FileBlock{} 209 t.Log("Request a block retrieval for ptr1 and ptr2.") 210 _ = q.Request( 211 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 212 data.NoCacheEntry, BlockRequestWithPrefetch) 213 _ = q.Request( 214 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr2, block, 215 data.NoCacheEntry, BlockRequestWithPrefetch) 216 217 t.Log("Begin working on the ptr1 request.") 218 br := q.popIfNotEmpty() 219 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 220 require.Equal(t, ptr1, br.blockPtr) 221 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 222 require.Equal(t, uint64(0), br.insertionOrder) 223 224 ptr3 := makeRandomBlockPointer(t) 225 t.Log("Preempt the ptr2 request with the ptr3 request.") 226 _ = q.Request( 227 ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr3, 228 block, data.NoCacheEntry, BlockRequestWithPrefetch) 229 230 t.Log("Begin working on the ptr3 request.") 231 br = q.popIfNotEmpty() 232 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 233 require.Equal(t, ptr3, br.blockPtr) 234 require.Equal(t, defaultOnDemandRequestPriority+1, br.priority) 235 require.Equal(t, uint64(2), br.insertionOrder) 236 237 t.Log("Begin working on the ptr2 request.") 238 br = q.popIfNotEmpty() 239 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 240 require.Equal(t, ptr2, br.blockPtr) 241 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 242 require.Equal(t, uint64(1), br.insertionOrder) 243 } 244 245 func TestBlockRetrievalQueueMultipleRequestsSameBlock(t *testing.T) { 246 t.Log("Request the same block multiple times.") 247 q := initBlockRetrievalQueueTest(t) 248 require.NotNil(t, q) 249 defer endBlockRetrievalQueueTest(t, q) 250 251 ctx := context.Background() 252 ptr1 := makeRandomBlockPointer(t) 253 block := &data.FileBlock{} 254 t.Log("Request a block retrieval for ptr1 twice.") 255 _ = q.Request( 256 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 257 data.NoCacheEntry, BlockRequestWithPrefetch) 258 _ = q.Request( 259 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 260 data.NoCacheEntry, BlockRequestWithPrefetch) 261 262 t.Log("Begin working on the ptr1 retrieval. Verify that it has 2 requests and that the queue is now empty.") 263 br := q.popIfNotEmpty() 264 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 265 require.Equal(t, ptr1, br.blockPtr) 266 require.Equal(t, -1, br.index) 267 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 268 require.Equal(t, uint64(0), br.insertionOrder) 269 require.Len(t, br.requests, 2) 270 require.Len(t, *q.heap, 0) 271 require.Equal(t, block, br.requests[0].block) 272 require.Equal(t, block, br.requests[1].block) 273 } 274 275 func TestBlockRetrievalQueueElevatePriorityExistingRequest(t *testing.T) { 276 t.Log("Elevate the priority on an existing request.") 277 q := initBlockRetrievalQueueTest(t) 278 require.NotNil(t, q) 279 defer endBlockRetrievalQueueTest(t, q) 280 281 ctx := context.Background() 282 ptr1 := makeRandomBlockPointer(t) 283 ptr2 := makeRandomBlockPointer(t) 284 ptr3 := makeRandomBlockPointer(t) 285 block := &data.FileBlock{} 286 t.Log("Request 3 block retrievals, each preempting the previous one.") 287 _ = q.Request( 288 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 289 data.NoCacheEntry, BlockRequestWithPrefetch) 290 _ = q.Request( 291 ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr2, 292 block, data.NoCacheEntry, BlockRequestWithPrefetch) 293 _ = q.Request( 294 ctx, defaultOnDemandRequestPriority+2, makeKMD(), ptr3, 295 block, data.NoCacheEntry, BlockRequestWithPrefetch) 296 297 t.Log("Begin working on the ptr3 retrieval.") 298 br := q.popIfNotEmpty() 299 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 300 require.Equal(t, ptr3, br.blockPtr) 301 require.Equal(t, defaultOnDemandRequestPriority+2, br.priority) 302 require.Equal(t, uint64(2), br.insertionOrder) 303 304 t.Log("Preempt the remaining retrievals with another retrieval for ptr1.") 305 _ = q.Request( 306 ctx, defaultOnDemandRequestPriority+2, makeKMD(), ptr1, 307 block, data.NoCacheEntry, BlockRequestWithPrefetch) 308 309 t.Log("Begin working on the ptr1 retrieval. Verify that it has increased in priority and has 2 requests.") 310 br = q.popIfNotEmpty() 311 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 312 require.Equal(t, ptr1, br.blockPtr) 313 require.Equal(t, defaultOnDemandRequestPriority+2, br.priority) 314 require.Equal(t, uint64(0), br.insertionOrder) 315 require.Len(t, br.requests, 2) 316 317 t.Log("Begin working on the ptr2 retrieval.") 318 br = q.popIfNotEmpty() 319 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 320 require.Equal(t, ptr2, br.blockPtr) 321 require.Equal(t, defaultOnDemandRequestPriority+1, br.priority) 322 require.Equal(t, uint64(1), br.insertionOrder) 323 } 324 325 func TestBlockRetrievalQueueCurrentlyProcessingRequest(t *testing.T) { 326 t.Log("Begin processing a request and then add another one for the same block.") 327 q := initBlockRetrievalQueueTest(t) 328 require.NotNil(t, q) 329 defer endBlockRetrievalQueueTest(t, q) 330 331 ctx := context.Background() 332 ptr1 := makeRandomBlockPointer(t) 333 block := &data.FileBlock{} 334 t.Log("Request a block retrieval for ptr1.") 335 _ = q.Request( 336 ctx, defaultOnDemandRequestPriority, makeKMD(), ptr1, block, 337 data.NoCacheEntry, BlockRequestWithPrefetch) 338 339 t.Log("Begin working on the ptr1 retrieval. Verify that it has 1 request.") 340 br := q.popIfNotEmpty() 341 require.Equal(t, ptr1, br.blockPtr) 342 require.Equal(t, -1, br.index) 343 require.Equal(t, defaultOnDemandRequestPriority, br.priority) 344 require.Equal(t, uint64(0), br.insertionOrder) 345 require.Len(t, br.requests, 1) 346 require.Equal(t, block, br.requests[0].block) 347 348 t.Log("Request another block retrieval for ptr1 before it has finished. " + 349 "Verify that the priority has elevated and there are now 2 requests.") 350 _ = q.Request( 351 ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr1, 352 block, data.NoCacheEntry, BlockRequestWithPrefetch) 353 require.Equal(t, defaultOnDemandRequestPriority+1, br.priority) 354 require.Equal(t, uint64(0), br.insertionOrder) 355 require.Len(t, br.requests, 2) 356 require.Equal(t, block, br.requests[0].block) 357 require.Equal(t, block, br.requests[1].block) 358 359 t.Log("Finalize the existing request for ptr1.") 360 q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, nil) 361 t.Log("Make another request for the same block. Verify that this is a new request.") 362 _ = q.Request( 363 ctx, defaultOnDemandRequestPriority+1, makeKMD(), ptr1, 364 block, data.NoCacheEntry, BlockRequestWithPrefetch) 365 br = q.popIfNotEmpty() 366 defer q.FinalizeRequest(br, &data.FileBlock{}, DiskBlockAnyCache, io.EOF) 367 require.Equal(t, defaultOnDemandRequestPriority+1, br.priority) 368 require.Equal(t, uint64(1), br.insertionOrder) 369 require.Len(t, br.requests, 1) 370 require.Equal(t, block, br.requests[0].block) 371 } 372 373 func TestBlockRetrievalQueueThrottling(t *testing.T) { 374 t.Log("Start test with no throttling channel so we can pass in our own") 375 q := initBlockRetrievalQueueTest(t) 376 require.NotNil(t, q) 377 defer endBlockRetrievalQueueTest(t, q) 378 379 throttleCh := channels.NewInfiniteChannel() 380 q.throttledWorkCh = throttleCh 381 382 t.Log("Make a few throttled requests that won't be serviced until we " + 383 "start the background loop.") 384 385 ctx := context.Background() 386 ptr1 := makeRandomBlockPointer(t) 387 block1 := &data.FileBlock{} 388 _ = q.Request( 389 ctx, throttleRequestPriority, makeKMD(), ptr1, block1, 390 data.NoCacheEntry, BlockRequestSolo) 391 ptr2 := makeRandomBlockPointer(t) 392 block2 := &data.FileBlock{} 393 _ = q.Request( 394 ctx, throttleRequestPriority-100, makeKMD(), ptr2, block2, 395 data.NoCacheEntry, BlockRequestSolo) 396 397 t.Log("Make sure they are queued to be throttled") 398 require.Equal(t, 2, throttleCh.Len()) 399 400 t.Log("Start background loop with short period") 401 go q.throttleReleaseLoop(1 * time.Millisecond) 402 ctx, cancel := context.WithTimeout(ctx, 1*time.Second) 403 defer cancel() 404 for throttleCh.Len() > 0 { 405 time.Sleep(1 * time.Millisecond) 406 select { 407 case <-ctx.Done(): 408 t.Fatal(ctx.Err()) 409 default: 410 } 411 } 412 }