github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/series_wired_list_interaction_test.go (about) 1 // Copyright (c) 2018 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 storage 22 23 import ( 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/m3db/m3/src/dbnode/namespace" 29 "github.com/m3db/m3/src/dbnode/runtime" 30 "github.com/m3db/m3/src/dbnode/storage/block" 31 "github.com/m3db/m3/src/dbnode/storage/series" 32 "github.com/m3db/m3/src/dbnode/ts" 33 "github.com/m3db/m3/src/x/clock" 34 "github.com/m3db/m3/src/x/context" 35 "github.com/m3db/m3/src/x/ident" 36 "github.com/m3db/m3/src/x/instrument" 37 "github.com/m3db/m3/src/x/pool" 38 xtest "github.com/m3db/m3/src/x/test" 39 xtime "github.com/m3db/m3/src/x/time" 40 41 "github.com/golang/mock/gomock" 42 "github.com/stretchr/testify/require" 43 ) 44 45 // TestSeriesWiredListConcurrentInteractions was added as a regression test 46 // after discovering that interactions between a single series and the wired 47 // list could trigger a mutual dead lock. Specifically, if the wired list event 48 // channel was full, then the series could get blocked on a call to list.Update() 49 // in the OnRetrieveBlockMethod while the only goroutine pulling items off of that 50 // channel was stuck on the same series OnEvictedFromWiredList method. In that case, 51 // the OnRetrieveBlockMethod was stuck on a channel send while holding a lock that was 52 // required for the OnEvictedFromWiredList method that the wired list worker routine 53 // was calling. 54 func TestSeriesWiredListConcurrentInteractions(t *testing.T) { 55 ctrl := xtest.NewController(t) 56 defer ctrl.Finish() 57 58 var ( 59 runtimeOptsMgr = runtime.NewOptionsManager() 60 runtimeOpts = runtime.NewOptions().SetMaxWiredBlocks(1) 61 ) 62 runtimeOptsMgr.Update(runtimeOpts) 63 64 runtime.NewOptions().SetMaxWiredBlocks(1) 65 wl := block.NewWiredList(block.WiredListOptions{ 66 RuntimeOptionsManager: runtimeOptsMgr, 67 InstrumentOptions: instrument.NewOptions(), 68 ClockOptions: clock.NewOptions(), 69 // Use a small channel to stress-test the implementation 70 EventsChannelSize: 1, 71 }) 72 wl.Start() 73 defer wl.Stop() 74 75 var ( 76 blOpts = DefaultTestOptions().DatabaseBlockOptions() 77 blPool = block.NewDatabaseBlockPool( 78 // Small pool size to make any pooling issues more 79 // likely to manifest. 80 pool.NewObjectPoolOptions().SetSize(5), 81 ) 82 ) 83 blPool.Init(func() block.DatabaseBlock { 84 return block.NewDatabaseBlock(0, 0, ts.Segment{}, blOpts, namespace.Context{}) 85 }) 86 87 blockRetriever := series.NewMockQueryableBlockRetriever(ctrl) 88 blockRetriever.EXPECT(). 89 IsBlockRetrievable(gomock.Any()). 90 Return(false, nil). 91 AnyTimes() 92 93 var ( 94 blockSize = time.Hour * 2 95 start = xtime.Now().Truncate(blockSize) 96 opts = DefaultTestOptions().SetDatabaseBlockOptions( 97 blOpts. 98 SetWiredList(wl). 99 SetDatabaseBlockPool(blPool), 100 ) 101 shard = testDatabaseShard(t, opts) 102 id = ident.StringID("foo") 103 seriesEntry = series.NewDatabaseSeries(series.DatabaseSeriesOptions{ 104 ID: id, 105 UniqueIndex: 1, 106 BlockRetriever: blockRetriever, 107 OnRetrieveBlock: shard.seriesOnRetrieveBlock, 108 OnEvictedFromWiredList: shard, 109 Options: shard.seriesOpts, 110 }) 111 block = block.NewDatabaseBlock(start, blockSize, ts.Segment{}, blOpts, namespace.Context{}) 112 ) 113 114 err := seriesEntry.LoadBlock(block, series.WarmWrite) 115 require.NoError(t, err) 116 117 shard.Lock() 118 shard.insertNewShardEntryWithLock(NewEntry(NewEntryOptions{ 119 Series: seriesEntry, 120 })) 121 shard.Unlock() 122 123 var ( 124 wg = sync.WaitGroup{} 125 doneCh = make(chan struct{}) 126 ) 127 go func() { 128 // Try and trigger any pooling issues 129 for { 130 select { 131 case <-doneCh: 132 return 133 default: 134 bl := blPool.Get() 135 bl.Close() 136 } 137 } 138 }() 139 140 var ( 141 startLock = sync.Mutex{} 142 getAndIncStart = func() xtime.UnixNano { 143 startLock.Lock() 144 t := start 145 start = start.Add(blockSize) 146 startLock.Unlock() 147 return t 148 } 149 ) 150 151 for i := 0; i < 1000; i++ { 152 wg.Add(1) 153 go func() { 154 blTime := getAndIncStart() 155 shard.OnRetrieveBlock(id, nil, blTime, ts.Segment{}, namespace.Context{}) 156 // Simulate concurrent reads 157 _, err := shard.ReadEncoded(context.NewBackground(), id, blTime, blTime.Add(blockSize), namespace.Context{}) 158 require.NoError(t, err) 159 wg.Done() 160 }() 161 } 162 163 wg.Wait() 164 close(doneCh) 165 }