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  }