github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/seek_manager_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package fs 22 23 import ( 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/cluster/shard" 29 "github.com/m3db/m3/src/dbnode/retention" 30 "github.com/m3db/m3/src/dbnode/sharding" 31 "github.com/m3db/m3/src/dbnode/storage/block" 32 "github.com/m3db/m3/src/x/ident" 33 xtest "github.com/m3db/m3/src/x/test" 34 xtime "github.com/m3db/m3/src/x/time" 35 36 "github.com/fortytw2/leaktest" 37 "github.com/golang/mock/gomock" 38 "github.com/stretchr/testify/require" 39 ) 40 41 const ( 42 defaultTestingFetchConcurrency = 2 43 ) 44 45 var defaultTestBlockRetrieverOptions = NewBlockRetrieverOptions(). 46 SetBlockLeaseManager(&block.NoopLeaseManager{}). 47 // Test with caching enabled. 48 SetCacheBlocksOnRetrieve(true). 49 // Default value is determined by available CPUs, but for testing 50 // we want to have this been consistent across hardware. 51 SetFetchConcurrency(defaultTestingFetchConcurrency) 52 53 func TestSeekerManagerCacheShardIndices(t *testing.T) { 54 defer leaktest.CheckTimeout(t, 1*time.Minute)() 55 56 shards := []uint32{2, 5, 9, 478, 1023} 57 metadata := testNs1Metadata(t) 58 shardSet, err := sharding.NewShardSet( 59 sharding.NewShards(shards, shard.Available), 60 sharding.DefaultHashFn(1), 61 ) 62 require.NoError(t, err) 63 m := NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 64 require.NoError(t, m.Open(metadata, shardSet)) 65 byTimes := make(map[uint32]*seekersByTime) 66 var mu sync.Mutex 67 m.openAnyUnopenSeekersFn = func(byTime *seekersByTime) error { 68 mu.Lock() 69 byTimes[byTime.shard] = byTime 70 mu.Unlock() 71 return nil 72 } 73 74 require.NoError(t, m.CacheShardIndices(shards)) 75 // Assert captured byTime objects match expectations 76 require.Equal(t, len(shards), len(byTimes)) 77 for _, shard := range shards { 78 mu.Lock() 79 byTimes[shard].shard = shard 80 mu.Unlock() 81 } 82 83 // Assert seeksByShardIdx match expectations 84 shardSetMap := make(map[uint32]struct{}, len(shards)) 85 for _, shard := range shards { 86 shardSetMap[shard] = struct{}{} 87 } 88 89 for shard, byTime := range m.seekersByShardIdx { 90 _, exists := shardSetMap[uint32(shard)] 91 if !exists { 92 require.False(t, byTime.accessed) 93 } else { 94 require.True(t, byTime.accessed) 95 require.Equal(t, int(shard), int(byTime.shard)) 96 } 97 } 98 99 require.NoError(t, m.Close()) 100 } 101 102 func TestSeekerManagerUpdateOpenLease(t *testing.T) { 103 defer leaktest.CheckTimeout(t, 1*time.Minute)() 104 105 var ( 106 ctrl = xtest.NewController(t) 107 shards = []uint32{2, 5, 9, 478, 1023} 108 m = NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 109 ) 110 defer ctrl.Finish() 111 112 var ( 113 mockSeekerStatsLock sync.Mutex 114 numMockSeekerCloses int 115 ) 116 m.newOpenSeekerFn = func( 117 shard uint32, 118 blockStart xtime.UnixNano, 119 volume int, 120 ) (DataFileSetSeeker, error) { 121 mock := NewMockDataFileSetSeeker(ctrl) 122 // ConcurrentClone() will be called fetchConcurrency-1 times because the original can be used 123 // as one of the clones. 124 for i := 0; i < defaultTestingFetchConcurrency-1; i++ { 125 mock.EXPECT().ConcurrentClone().Return(mock, nil) 126 } 127 for i := 0; i < defaultTestingFetchConcurrency; i++ { 128 mock.EXPECT().Close().DoAndReturn(func() error { 129 mockSeekerStatsLock.Lock() 130 numMockSeekerCloses++ 131 mockSeekerStatsLock.Unlock() 132 return nil 133 }) 134 mock.EXPECT().ConcurrentIDBloomFilter().Return(nil).AnyTimes() 135 } 136 return mock, nil 137 } 138 m.sleepFn = func(_ time.Duration) { 139 time.Sleep(time.Millisecond) 140 } 141 142 metadata := testNs1Metadata(t) 143 shardSet, err := sharding.NewShardSet( 144 sharding.NewShards(shards, shard.Available), 145 sharding.DefaultHashFn(1), 146 ) 147 require.NoError(t, err) 148 // Pick a start time that's within retention so the background loop doesn't close 149 // the seeker. 150 blockStart := xtime.Now().Truncate(metadata.Options().RetentionOptions().BlockSize()) 151 require.NoError(t, m.Open(metadata, shardSet)) 152 for _, shard := range shards { 153 seeker, err := m.Borrow(shard, blockStart) 154 require.NoError(t, err) 155 byTime, ok := m.seekersByTime(shard) 156 require.True(t, ok) 157 byTime.RLock() 158 seekers := byTime.seekers[blockStart] 159 require.Equal(t, defaultTestingFetchConcurrency, len(seekers.active.seekers)) 160 require.Equal(t, 0, seekers.active.volume) 161 byTime.RUnlock() 162 require.NoError(t, m.Return(shard, blockStart, seeker)) 163 } 164 165 // Ensure that UpdateOpenLease() updates the volumes. 166 for _, shard := range shards { 167 updateResult, err := m.UpdateOpenLease(block.LeaseDescriptor{ 168 Namespace: metadata.ID(), 169 Shard: shard, 170 BlockStart: blockStart, 171 }, block.LeaseState{Volume: 1}) 172 require.NoError(t, err) 173 require.Equal(t, block.UpdateOpenLease, updateResult) 174 175 byTime, ok := m.seekersByTime(shard) 176 require.True(t, ok) 177 byTime.RLock() 178 seekers := byTime.seekers[blockStart] 179 require.Equal(t, defaultTestingFetchConcurrency, len(seekers.active.seekers)) 180 require.Equal(t, 1, seekers.active.volume) 181 byTime.RUnlock() 182 } 183 // Ensure that the old seekers actually get closed. 184 mockSeekerStatsLock.Lock() 185 require.Equal(t, len(shards)*defaultTestingFetchConcurrency, numMockSeekerCloses) 186 mockSeekerStatsLock.Unlock() 187 188 // Ensure that UpdateOpenLease() ignores updates for the wrong namespace. 189 for _, shard := range shards { 190 updateResult, err := m.UpdateOpenLease(block.LeaseDescriptor{ 191 Namespace: ident.StringID("some-other-ns"), 192 Shard: shard, 193 BlockStart: blockStart, 194 }, block.LeaseState{Volume: 2}) 195 require.NoError(t, err) 196 require.Equal(t, block.NoOpenLease, updateResult) 197 198 byTime, ok := m.seekersByTime(shard) 199 require.True(t, ok) 200 byTime.RLock() 201 seekers := byTime.seekers[blockStart] 202 require.Equal(t, defaultTestingFetchConcurrency, len(seekers.active.seekers)) 203 // Should not have increased to 2. 204 require.Equal(t, 1, seekers.active.volume) 205 byTime.RUnlock() 206 } 207 208 // Ensure that UpdateOpenLease() returns an error for out-of-order updates. 209 for _, shard := range shards { 210 _, err := m.UpdateOpenLease(block.LeaseDescriptor{ 211 Namespace: metadata.ID(), 212 Shard: shard, 213 BlockStart: blockStart, 214 }, block.LeaseState{Volume: 0}) 215 require.Equal(t, errOutOfOrderUpdateOpenLease, err) 216 } 217 218 require.NoError(t, m.Close()) 219 } 220 221 func TestSeekerManagerUpdateOpenLeaseConcurrentNotAllowed(t *testing.T) { 222 defer leaktest.CheckTimeout(t, 1*time.Minute)() 223 224 var ( 225 ctrl = xtest.NewController(t) 226 shards = []uint32{1, 2} 227 m = NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 228 metadata = testNs1Metadata(t) 229 // Pick a start time that's within retention so the background loop doesn't close the seeker. 230 blockStart = xtime.Now().Truncate(metadata.Options().RetentionOptions().BlockSize()) 231 ) 232 defer ctrl.Finish() 233 234 descriptor1 := block.LeaseDescriptor{ 235 Namespace: metadata.ID(), 236 Shard: 1, 237 BlockStart: blockStart, 238 } 239 240 m.newOpenSeekerFn = func( 241 shard uint32, 242 blockStart xtime.UnixNano, 243 volume int, 244 ) (DataFileSetSeeker, error) { 245 if volume == 1 { 246 var wg sync.WaitGroup 247 wg.Add(1) 248 go func() { 249 defer wg.Done() 250 // Call UpdateOpenLease while within another UpdateOpenLease call. 251 _, err := m.UpdateOpenLease(descriptor1, block.LeaseState{Volume: 2}) 252 if shard == 1 { 253 // Concurrent call is made with the same shard id (and other values). 254 require.Equal(t, errConcurrentUpdateOpenLeaseNotAllowed, err) 255 } else { 256 // Concurrent call is made with a different shard id (2) and so it should pass. 257 require.NoError(t, err) 258 } 259 }() 260 wg.Wait() 261 } 262 mock := NewMockDataFileSetSeeker(ctrl) 263 mock.EXPECT().ConcurrentClone().Return(mock, nil).AnyTimes() 264 mock.EXPECT().Close().AnyTimes() 265 mock.EXPECT().ConcurrentIDBloomFilter().Return(nil).AnyTimes() 266 return mock, nil 267 } 268 m.sleepFn = func(_ time.Duration) { 269 time.Sleep(time.Millisecond) 270 } 271 272 shardSet, err := sharding.NewShardSet( 273 sharding.NewShards(shards, shard.Available), 274 sharding.DefaultHashFn(1), 275 ) 276 require.NoError(t, err) 277 require.NoError(t, m.Open(metadata, shardSet)) 278 279 for _, shardID := range shards { 280 seeker, err := m.Borrow(shardID, blockStart) 281 require.NoError(t, err) 282 require.NoError(t, m.Return(shardID, blockStart, seeker)) 283 } 284 285 updateResult, err := m.UpdateOpenLease(descriptor1, block.LeaseState{Volume: 1}) 286 require.NoError(t, err) 287 require.Equal(t, block.UpdateOpenLease, updateResult) 288 289 descriptor2 := descriptor1 290 descriptor2.Shard = 2 291 updateResult, err = m.UpdateOpenLease(descriptor2, block.LeaseState{Volume: 1}) 292 require.NoError(t, err) 293 require.Equal(t, block.UpdateOpenLease, updateResult) 294 295 require.NoError(t, m.Close()) 296 } 297 298 // TestSeekerManagerBorrowOpenSeekersLazy tests that the Borrow() method will 299 // open seekers lazily if they're not already open. 300 func TestSeekerManagerBorrowOpenSeekersLazy(t *testing.T) { 301 defer leaktest.CheckTimeout(t, 1*time.Minute)() 302 303 ctrl := xtest.NewController(t) 304 305 shards := []uint32{2, 5, 9, 478, 1023} 306 m := NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 307 m.newOpenSeekerFn = func( 308 shard uint32, 309 blockStart xtime.UnixNano, 310 volume int, 311 ) (DataFileSetSeeker, error) { 312 mock := NewMockDataFileSetSeeker(ctrl) 313 mock.EXPECT().Open(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) 314 mock.EXPECT().ConcurrentClone().Return(mock, nil) 315 for i := 0; i < defaultTestingFetchConcurrency; i++ { 316 mock.EXPECT().Close().Return(nil) 317 mock.EXPECT().ConcurrentIDBloomFilter().Return(nil) 318 } 319 return mock, nil 320 } 321 m.sleepFn = func(_ time.Duration) { 322 time.Sleep(time.Millisecond) 323 } 324 325 metadata := testNs1Metadata(t) 326 shardSet, err := sharding.NewShardSet( 327 sharding.NewShards(shards, shard.Available), 328 sharding.DefaultHashFn(1), 329 ) 330 require.NoError(t, err) 331 require.NoError(t, m.Open(metadata, shardSet)) 332 for _, shard := range shards { 333 seeker, err := m.Borrow(shard, 0) 334 require.NoError(t, err) 335 byTime, ok := m.seekersByTime(shard) 336 require.True(t, ok) 337 byTime.RLock() 338 seekers := byTime.seekers[0] 339 require.Equal(t, defaultTestingFetchConcurrency, len(seekers.active.seekers)) 340 byTime.RUnlock() 341 require.NoError(t, m.Return(shard, 0, seeker)) 342 } 343 344 require.NoError(t, m.Close()) 345 } 346 347 // TestSeekerManagerOpenCloseLoop tests the openCloseLoop of the SeekerManager 348 // by making sure that it makes the right decisions with regards to cleaning 349 // up resources based on their state. 350 func TestSeekerManagerOpenCloseLoop(t *testing.T) { 351 defer leaktest.CheckTimeout(t, 1*time.Minute)() 352 353 ctrl := xtest.NewController(t) 354 m := NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 355 clockOpts := m.opts.ClockOptions() 356 now := clockOpts.NowFn()() 357 startNano := xtime.ToUnixNano(now) 358 359 fakeTime := now 360 fakeTimeLock := sync.Mutex{} 361 // Setup a function that will allow us to dynamically modify the clock in 362 // a concurrency-safe way 363 newNowFn := func() time.Time { 364 fakeTimeLock.Lock() 365 defer fakeTimeLock.Unlock() 366 return fakeTime 367 } 368 clockOpts = clockOpts.SetNowFn(newNowFn) 369 m.opts = m.opts.SetClockOptions(clockOpts) 370 371 // Initialize some seekers for a time period 372 m.openAnyUnopenSeekersFn = func(byTime *seekersByTime) error { 373 byTime.Lock() 374 defer byTime.Unlock() 375 376 // Don't overwrite if called again 377 if len(byTime.seekers) != 0 { 378 return nil 379 } 380 381 // Don't re-open if they should have expired 382 fakeTimeLock.Lock() 383 defer fakeTimeLock.Unlock() 384 if !fakeTime.Equal(now) { 385 return nil 386 } 387 388 mock := NewMockDataFileSetSeeker(ctrl) 389 mock.EXPECT().Close().Return(nil) 390 mocks := []borrowableSeeker{} 391 mocks = append(mocks, borrowableSeeker{seeker: mock}) 392 byTime.seekers[startNano] = rotatableSeekers{ 393 active: seekersAndBloom{ 394 seekers: mocks, 395 bloomFilter: nil, 396 }, 397 } 398 return nil 399 } 400 401 // Notified everytime the openCloseLoop ticks 402 tickCh := make(chan struct{}) 403 cleanupCh := make(chan struct{}) 404 405 m.sleepFn = func(_ time.Duration) { 406 tickCh <- struct{}{} 407 } 408 409 shards := []uint32{2, 5, 9, 478, 1023} 410 metadata := testNs1Metadata(t) 411 shardSet, err := sharding.NewShardSet( 412 sharding.NewShards(shards, shard.Available), 413 sharding.DefaultHashFn(1), 414 ) 415 require.NoError(t, err) 416 require.NoError(t, m.Open(metadata, shardSet)) 417 418 // Force all the seekers to be opened 419 require.NoError(t, m.CacheShardIndices(shards)) 420 421 seekers := []ConcurrentDataFileSetSeeker{} 422 423 // Steps is a series of steps for the test. It is guaranteed that at least 424 // one (not exactly one!) tick of the openCloseLoop will occur between every step. 425 steps := []struct { 426 title string 427 step func() 428 }{ 429 { 430 title: "Make sure it didn't clean up the seekers which are still in retention", 431 step: func() { 432 m.RLock() 433 for _, shard := range shards { 434 byTime, ok := m.seekersByTime(shard) 435 require.True(t, ok) 436 437 require.Equal(t, 1, len(byTime.seekers[startNano].active.seekers)) 438 } 439 m.RUnlock() 440 }, 441 }, 442 { 443 title: "Borrow a seeker from each shard and then modify the clock such that they're out of retention", 444 step: func() { 445 for _, shard := range shards { 446 seeker, err := m.Borrow(shard, startNano) 447 require.NoError(t, err) 448 require.NotNil(t, seeker) 449 seekers = append(seekers, seeker) 450 } 451 452 fakeTimeLock.Lock() 453 fakeTime = fakeTime.Add(10 * metadata.Options().RetentionOptions().RetentionPeriod()) 454 fakeTimeLock.Unlock() 455 }, 456 }, 457 { 458 title: "Make sure the seeker manager cant be closed while seekers are borrowed", 459 step: func() { 460 require.Equal(t, errCantCloseSeekerManagerWhileSeekersAreBorrowed, m.Close()) 461 }, 462 }, 463 { 464 title: "Make sure that none of the seekers were cleaned up during the openCloseLoop tick (because they're still borrowed)", 465 step: func() { 466 m.RLock() 467 for _, shard := range shards { 468 byTime, ok := m.seekersByTime(shard) 469 require.True(t, ok) 470 require.Equal(t, 1, len(byTime.seekers[startNano].active.seekers)) 471 } 472 m.RUnlock() 473 }, 474 }, 475 { 476 title: "Return the borrowed seekers", 477 step: func() { 478 for i, seeker := range seekers { 479 require.NoError(t, m.Return(shards[i], startNano, seeker)) 480 } 481 }, 482 }, 483 { 484 title: "Make sure that the returned seekers were cleaned up during the openCloseLoop tick", 485 step: func() { 486 m.RLock() 487 for _, shard := range shards { 488 byTime, ok := m.seekersByTime(shard) 489 require.True(t, ok) 490 byTime.RLock() 491 _, ok = byTime.seekers[startNano] 492 byTime.RUnlock() 493 require.False(t, ok) 494 } 495 m.RUnlock() 496 }, 497 }, 498 } 499 500 for _, step := range steps { 501 // Wait for two notifications between steps to guarantee that the entirety 502 // of the openCloseLoop is executed at least once 503 <-tickCh 504 <-tickCh 505 step.step() 506 } 507 508 // Background goroutine that will pull notifications off the tickCh so that 509 // the openCloseLoop is not blocked when we call Close() 510 go func() { 511 for { 512 select { 513 case <-tickCh: 514 continue 515 case <-cleanupCh: 516 return 517 } 518 } 519 }() 520 521 // Restore previous interval once the openCloseLoop ends 522 require.NoError(t, m.Close()) 523 // Make sure there are no goroutines still trying to write into the tickCh 524 // to prevent the test itself from interfering with the goroutine leak test 525 close(cleanupCh) 526 } 527 528 func TestSeekerManagerAssignShardSet(t *testing.T) { 529 defer leaktest.CheckTimeout(t, 1*time.Minute)() 530 531 var ( 532 ctrl = xtest.NewController(t) 533 shards = []uint32{1, 2} 534 m = NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 535 ) 536 defer ctrl.Finish() 537 538 var ( 539 wg sync.WaitGroup 540 mockSeekerStatsLock sync.Mutex 541 numMockSeekerClosesByShardAndBlockStart = make(map[uint32]map[xtime.UnixNano]int) 542 ) 543 m.newOpenSeekerFn = func( 544 shard uint32, 545 blockStart xtime.UnixNano, 546 volume int, 547 ) (DataFileSetSeeker, error) { 548 // We expect `defaultTestingFetchConcurrency` number of calls to Close because we return this 549 // many numbers of clones and each clone will need to be closed. 550 wg.Add(defaultTestingFetchConcurrency) 551 552 mock := NewMockDataFileSetSeeker(ctrl) 553 // ConcurrentClone() will be called fetchConcurrency-1 times because the original can be used 554 // as one of the clones. 555 mock.EXPECT().ConcurrentClone().Times(defaultTestingFetchConcurrency-1).Return(mock, nil) 556 mock.EXPECT().Close().Times(defaultTestingFetchConcurrency).DoAndReturn(func() error { 557 mockSeekerStatsLock.Lock() 558 numMockSeekerClosesByBlockStart, ok := numMockSeekerClosesByShardAndBlockStart[shard] 559 if !ok { 560 numMockSeekerClosesByBlockStart = make(map[xtime.UnixNano]int) 561 numMockSeekerClosesByShardAndBlockStart[shard] = numMockSeekerClosesByBlockStart 562 } 563 numMockSeekerClosesByBlockStart[blockStart]++ 564 mockSeekerStatsLock.Unlock() 565 wg.Done() 566 return nil 567 }) 568 mock.EXPECT().ConcurrentIDBloomFilter().Return(nil).AnyTimes() 569 return mock, nil 570 } 571 m.sleepFn = func(_ time.Duration) { 572 time.Sleep(time.Millisecond) 573 } 574 575 metadata := testNs1Metadata(t) 576 shardSet, err := sharding.NewShardSet( 577 sharding.NewShards(shards, shard.Available), 578 sharding.DefaultHashFn(1), 579 ) 580 require.NoError(t, err) 581 // Pick a start time thats within retention so the background loop doesn't close 582 // the seeker. 583 blockStart := xtime.Now().Truncate(metadata.Options().RetentionOptions().BlockSize()) 584 require.NoError(t, m.Open(metadata, shardSet)) 585 586 for _, shard := range shards { 587 seeker, err := m.Borrow(shard, blockStart) 588 require.NoError(t, err) 589 require.NoError(t, m.Return(shard, blockStart, seeker)) 590 } 591 592 // Ensure that UpdateOpenLease() updates the volumes. 593 for _, shard := range shards { 594 updateResult, err := m.UpdateOpenLease(block.LeaseDescriptor{ 595 Namespace: metadata.ID(), 596 Shard: shard, 597 BlockStart: blockStart, 598 }, block.LeaseState{Volume: 1}) 599 require.NoError(t, err) 600 require.Equal(t, block.UpdateOpenLease, updateResult) 601 602 byTime, ok := m.seekersByTime(shard) 603 require.True(t, ok) 604 byTime.RLock() 605 byTime.RUnlock() 606 } 607 608 mockSeekerStatsLock.Lock() 609 for _, numMockSeekerClosesByBlockStart := range numMockSeekerClosesByShardAndBlockStart { 610 require.Equal(t, 611 defaultTestingFetchConcurrency, 612 numMockSeekerClosesByBlockStart[blockStart]) 613 } 614 mockSeekerStatsLock.Unlock() 615 616 // Shards have moved off the node so we assign an empty shard set. 617 m.AssignShardSet(sharding.NewEmptyShardSet(sharding.DefaultHashFn(1))) 618 // Wait until the open/close loop has finished closing all the shards marked to be closed. 619 wg.Wait() 620 621 // Verify that shards are no longer available. 622 for _, shard := range shards { 623 ok, err := m.Test(nil, shard, blockStart) 624 require.Equal(t, errShardNotExists, err) 625 require.False(t, ok) 626 _, err = m.Borrow(shard, blockStart) 627 require.Equal(t, errShardNotExists, err) 628 } 629 630 // Verify that we see the expected # of closes per block start. 631 mockSeekerStatsLock.Lock() 632 for _, numMockSeekerClosesByBlockStart := range numMockSeekerClosesByShardAndBlockStart { 633 for start, numMockSeekerCloses := range numMockSeekerClosesByBlockStart { 634 if blockStart == start { 635 // NB(bodu): These get closed twice since they've been closed once already due to updating their block lease. 636 require.Equal(t, defaultTestingFetchConcurrency*2, numMockSeekerCloses) 637 continue 638 } 639 require.Equal(t, defaultTestingFetchConcurrency, numMockSeekerCloses) 640 } 641 } 642 mockSeekerStatsLock.Unlock() 643 644 // Shards have moved back to the node so we assign a populated shard set again. 645 m.AssignShardSet(shardSet) 646 // Ensure that we can (once again) borrow the shards. 647 for _, shard := range shards { 648 seeker, err := m.Borrow(shard, blockStart) 649 require.NoError(t, err) 650 require.NoError(t, m.Return(shard, blockStart, seeker)) 651 } 652 653 require.NoError(t, m.Close()) 654 } 655 656 // TestSeekerManagerCacheShardIndicesSkipNotFound tests that expired (not found) index filesets 657 // do not return an error. 658 func TestSeekerManagerCacheShardIndicesSkipNotFound(t *testing.T) { 659 defer leaktest.CheckTimeout(t, 1*time.Minute)() 660 661 m := NewSeekerManager(nil, testDefaultOpts, defaultTestBlockRetrieverOptions).(*seekerManager) 662 663 m.newOpenSeekerFn = func( 664 shard uint32, 665 blockStart xtime.UnixNano, 666 volume int, 667 ) (DataFileSetSeeker, error) { 668 return nil, errSeekerManagerFileSetNotFound 669 } 670 671 shards := []uint32{2, 5, 9, 478, 1023} 672 metadata := testNs1Metadata(t) 673 shardSet, err := sharding.NewShardSet( 674 sharding.NewShards(shards, shard.Available), 675 sharding.DefaultHashFn(1), 676 ) 677 require.NoError(t, err) 678 require.NoError(t, m.Open(metadata, shardSet)) 679 680 require.NoError(t, m.CacheShardIndices(shards)) 681 682 require.NoError(t, m.Close()) 683 } 684 685 func TestSeekerManagerDoNotOpenSeekersForOutOfRetentionBlocks(t *testing.T) { 686 defer leaktest.CheckTimeout(t, 1*time.Minute)() 687 var ( 688 ctrl = xtest.NewController(t) 689 shards = []uint32{0} 690 metadata = testNs1Metadata(t) 691 rOpts = metadata.Options().RetentionOptions() 692 blockSize = rOpts.BlockSize() 693 signal = make(chan struct{}) 694 openSeekers = make(map[xtime.UnixNano]struct{}) 695 now = time.Now() 696 opts = NewOptions() 697 ) 698 shardSet, err := sharding.NewShardSet( 699 sharding.NewShards(shards, shard.Available), 700 sharding.DefaultHashFn(1), 701 ) 702 require.NoError(t, err) 703 opts = opts.SetClockOptions(opts.ClockOptions().SetNowFn(func() time.Time { 704 return now 705 })) 706 m := NewSeekerManager(nil, opts, defaultTestBlockRetrieverOptions).(*seekerManager) 707 m.sleepFn = func(_ time.Duration) { 708 signal <- struct{}{} // signal once to indicate that openCloseLoop completed. 709 m.sleepFn = time.Sleep 710 } 711 require.NoError(t, m.Open(metadata, shardSet)) 712 defer func() { 713 require.NoError(t, m.Close()) 714 }() 715 716 m.newOpenSeekerFn = func(shard uint32, blockStart xtime.UnixNano, volume int) (DataFileSetSeeker, error) { 717 openSeekers[blockStart] = struct{}{} 718 mockSeeker := NewMockDataFileSetSeeker(ctrl) 719 mockConcurrentDataFileSetSeeker := NewMockConcurrentDataFileSetSeeker(ctrl) 720 mockConcurrentDataFileSetSeeker.EXPECT().Close().Return(nil) 721 mockSeeker.EXPECT().ConcurrentClone().Return(mockConcurrentDataFileSetSeeker, nil) 722 mockSeeker.EXPECT().ConcurrentIDBloomFilter().Return(nil) 723 mockSeeker.EXPECT().Close().Return(nil) 724 return mockSeeker, nil 725 } 726 727 earliestBlockStart := retention.FlushTimeStart(rOpts, xtime.ToUnixNano(now)) 728 require.NoError(t, m.CacheShardIndices(shards)) 729 730 <-signal 731 require.Contains(t, openSeekers, earliestBlockStart) 732 require.Contains(t, openSeekers, earliestBlockStart.Add(blockSize)) 733 require.NotContains(t, openSeekers, earliestBlockStart.Add(-blockSize)) 734 require.NotContains(t, openSeekers, earliestBlockStart.Add(-2*blockSize)) 735 }