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  }