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  }