github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/integration/series_wired_list_panic_test.go (about) 1 //go:build integration 2 // +build integration 3 4 // Copyright (c) 2021 Uber Technologies, Inc. 5 // 6 // Permission is hereby granted, free of charge, to any person obtaining a copy 7 // of this software and associated documentation files (the "Software"), to deal 8 // in the Software without restriction, including without limitation the rights 9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 // copies of the Software, and to permit persons to whom the Software is 11 // furnished to do so, subject to the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be included in 14 // all copies or substantial portions of the Software. 15 // 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 // THE SOFTWARE. 23 24 package integration 25 26 import ( 27 "fmt" 28 "testing" 29 "time" 30 31 "github.com/m3db/m3/src/dbnode/generated/thrift/rpc" 32 "github.com/m3db/m3/src/dbnode/integration/generate" 33 "github.com/m3db/m3/src/dbnode/namespace" 34 "github.com/m3db/m3/src/dbnode/persist/fs" 35 "github.com/m3db/m3/src/dbnode/sharding" 36 "github.com/m3db/m3/src/dbnode/storage" 37 "github.com/m3db/m3/src/dbnode/storage/block" 38 "github.com/m3db/m3/src/x/ident" 39 xtime "github.com/m3db/m3/src/x/time" 40 41 "github.com/stretchr/testify/require" 42 ) 43 44 const ( 45 numSeries = 10 46 ) 47 48 var nsID = ident.StringID("ns0") 49 50 func TestWiredListPanic(t *testing.T) { 51 // This test is used to repro https://github.com/m3db/m3/issues/2573. 52 // Unfortunately, this bug is due to a race condition and this test does not 53 // consistently reproduce it reliably in short period of time. As such, the 54 // test is configured to run for a very long duration to see if the repro 55 // occurs. Comment out the below SkipNow() to actually run this. 56 t.SkipNow() 57 58 // Small increment to make race condition more likely. 59 tickInterval := 5 * time.Millisecond 60 61 nsOpts := namespace.NewOptions(). 62 SetRepairEnabled(false). 63 SetRetentionOptions(DefaultIntegrationTestRetentionOpts). 64 SetCacheBlocksOnRetrieve(true) 65 ns, err := namespace.NewMetadata(nsID, nsOpts) 66 require.NoError(t, err) 67 testOpts := NewTestOptions(t). 68 SetTickMinimumInterval(tickInterval). 69 SetTickCancellationCheckInterval(tickInterval). 70 SetNamespaces([]namespace.Metadata{ns}). 71 // Wired list size of one means that if we query for two different IDs 72 // alternating between each one, we'll evict from the wired list on 73 // every query. 74 SetMaxWiredBlocks(1) 75 76 testSetup, err := NewTestSetup(t, testOpts, nil, 77 func(opts storage.Options) storage.Options { 78 return opts.SetMediatorTickInterval(tickInterval) 79 }, 80 func(opts storage.Options) storage.Options { 81 blockRetrieverMgr := block.NewDatabaseBlockRetrieverManager( 82 func( 83 md namespace.Metadata, 84 shardSet sharding.ShardSet, 85 ) (block.DatabaseBlockRetriever, error) { 86 retrieverOpts := fs.NewBlockRetrieverOptions(). 87 SetBlockLeaseManager(opts.BlockLeaseManager()). 88 SetCacheBlocksOnRetrieve(true) 89 retriever, err := fs.NewBlockRetriever(retrieverOpts, 90 opts.CommitLogOptions().FilesystemOptions()) 91 if err != nil { 92 return nil, err 93 } 94 95 if err := retriever.Open(md, shardSet); err != nil { 96 return nil, err 97 } 98 return retriever, nil 99 }) 100 return opts.SetDatabaseBlockRetrieverManager(blockRetrieverMgr) 101 }, 102 ) 103 104 require.NoError(t, err) 105 defer testSetup.Close() 106 107 // Start the server. 108 log := testSetup.StorageOpts().InstrumentOptions().Logger() 109 require.NoError(t, testSetup.StartServer()) 110 log.Info("server is now up") 111 112 // Stop the server. 113 defer func() { 114 require.NoError(t, testSetup.StopServer()) 115 log.Info("server is now down") 116 }() 117 118 md := testSetup.NamespaceMetadataOrFail(nsID) 119 ropts := md.Options().RetentionOptions() 120 blockSize := ropts.BlockSize() 121 filePathPrefix := testSetup.StorageOpts().CommitLogOptions().FilesystemOptions().FilePathPrefix() 122 123 seriesStrs := make([]string, 0, numSeries) 124 for i := 0; i < numSeries; i++ { 125 seriesStrs = append(seriesStrs, fmt.Sprintf("series-%d", i)) 126 } 127 128 start := testSetup.NowFn()() 129 go func() { 130 for i := 0; true; i++ { 131 write(t, testSetup, blockSize, start, filePathPrefix, i, seriesStrs) 132 time.Sleep(5 * time.Millisecond) 133 } 134 }() 135 136 doneCh := make(chan struct{}) 137 go func() { 138 for { 139 select { 140 case <-doneCh: 141 return 142 default: 143 read(t, testSetup, blockSize, seriesStrs) 144 time.Sleep(5 * time.Millisecond) 145 } 146 } 147 }() 148 149 time.Sleep(time.Hour) 150 // Stop reads before tearing down testSetup. 151 doneCh <- struct{}{} 152 } 153 154 func write( 155 t *testing.T, 156 testSetup TestSetup, 157 blockSize time.Duration, 158 start xtime.UnixNano, 159 filePathPrefix string, 160 i int, 161 seriesStrs []string, 162 ) { 163 blockStart := start.Add(time.Duration(2*i) * blockSize) 164 testSetup.SetNowFn(blockStart) 165 166 input := generate.BlockConfig{ 167 IDs: seriesStrs, NumPoints: 1, Start: blockStart, 168 } 169 testData := generate.Block(input) 170 require.NoError(t, testSetup.WriteBatch(nsID, testData)) 171 172 // Progress well past the block boundary so that the series gets flushed to 173 // disk. This allows the next tick to purge the series from memory, closing 174 // the series and thus making the id nil. 175 testSetup.SetNowFn(blockStart.Add(blockSize * 3 / 2)) 176 require.NoError(t, waitUntilFileSetFilesExist( 177 filePathPrefix, 178 []fs.FileSetFileIdentifier{ 179 { 180 Namespace: nsID, 181 Shard: 1, 182 BlockStart: blockStart, 183 VolumeIndex: 0, 184 }, 185 }, 186 time.Second, 187 )) 188 } 189 190 func read( 191 t *testing.T, 192 testSetup TestSetup, 193 blockSize time.Duration, 194 seriesStrs []string, 195 ) { 196 // After every write, "now" would be progressed into the future so that the 197 // will be flushed to disk. This makes "now" a suitable RangeEnd for the 198 // fetch request. The precise range does not matter so long as it overlaps 199 // with the current retention. 200 now := testSetup.NowFn()() 201 202 req := rpc.NewFetchRequest() 203 req.NameSpace = nsID.String() 204 req.RangeStart = now.Add(-4 * blockSize).Seconds() 205 req.RangeEnd = now.Seconds() 206 req.ResultTimeType = rpc.TimeType_UNIX_SECONDS 207 208 // Fetching the series sequentially ensures that the wired list will have 209 // evictions assuming that the list is configured with a size of 1. 210 for _, seriesStr := range seriesStrs { 211 req.ID = seriesStr 212 _, err := testSetup.Fetch(req) 213 require.NoError(t, err) 214 } 215 }