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