github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/client/writer_mgr_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 client
    22  
    23  import (
    24  	"errors"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"go.uber.org/atomic"
    33  	"go.uber.org/goleak"
    34  
    35  	"github.com/m3db/m3/src/cluster/placement"
    36  	"github.com/m3db/m3/src/x/clock"
    37  )
    38  
    39  var (
    40  	testPlacementInstance = placement.NewInstance().
    41  		SetID("testInstanceID").
    42  		SetEndpoint("testInstanceAddress")
    43  )
    44  
    45  func TestWriterManagerAddInstancesClosed(t *testing.T) {
    46  	mgr := mustMakeInstanceWriterManager(testOptions())
    47  	mgr.Lock()
    48  	mgr.closed = true
    49  	mgr.Unlock()
    50  	require.Equal(t, errInstanceWriterManagerClosed, mgr.AddInstances(nil))
    51  }
    52  
    53  func TestWriterManagerAddInstancesSingleRef(t *testing.T) {
    54  	mgr := mustMakeInstanceWriterManager(testOptions())
    55  
    56  	// Add instance lists twice and assert the writer refcount matches expectation.
    57  	for i := 0; i < 2; i++ {
    58  		require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance}))
    59  	}
    60  	mgr.Lock()
    61  	require.Equal(t, 1, len(mgr.writers))
    62  	w, exists := mgr.writers[testPlacementInstance.ID()]
    63  	mgr.Unlock()
    64  	require.True(t, exists)
    65  	require.Equal(t, int32(2), w.refCount.n)
    66  }
    67  
    68  func TestWriterManagerRemoveInstancesClosed(t *testing.T) {
    69  	mgr := mustMakeInstanceWriterManager(testOptions())
    70  	mgr.Lock()
    71  	mgr.closed = true
    72  	mgr.Unlock()
    73  	require.Equal(t, errInstanceWriterManagerClosed, mgr.RemoveInstances(nil))
    74  }
    75  
    76  func TestWriterManagerRemoveInstancesSuccess(t *testing.T) {
    77  	mgr := mustMakeInstanceWriterManager(testOptions())
    78  
    79  	// Add instance lists twice.
    80  	for i := 0; i < 2; i++ {
    81  		require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance}))
    82  	}
    83  	mgr.Lock()
    84  	require.Equal(t, 1, len(mgr.writers))
    85  	mgr.Unlock()
    86  
    87  	// Remove the instance list once and assert they are not closed.
    88  	require.NoError(t, mgr.RemoveInstances([]placement.Instance{testPlacementInstance}))
    89  
    90  	mgr.Lock()
    91  	require.Equal(t, 1, len(mgr.writers))
    92  	w := mgr.writers[testPlacementInstance.ID()].instanceWriter.(*writer)
    93  	require.False(t, w.closed)
    94  	mgr.Unlock()
    95  
    96  	// Remove the instance list again and assert the writer is now removed.
    97  	nonexistent := placement.NewInstance().
    98  		SetID("nonexistent").
    99  		SetEndpoint("nonexistentAddress")
   100  	toRemove := append([]placement.Instance{nonexistent, testPlacementInstance})
   101  	require.NoError(t, mgr.RemoveInstances(toRemove))
   102  	require.Equal(t, 0, len(mgr.writers))
   103  	require.True(t, clock.WaitUntil(func() bool {
   104  		w.Lock()
   105  		defer w.Unlock()
   106  		return w.closed
   107  	}, 3*time.Second))
   108  }
   109  
   110  func TestWriterManagerRemoveInstancesNonBlocking(t *testing.T) {
   111  	var (
   112  		opts = testOptions().SetInstanceQueueSize(200)
   113  		mgr  = mustMakeInstanceWriterManager(opts)
   114  	)
   115  	require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance}))
   116  
   117  	mgr.Lock()
   118  	require.Equal(t, 1, len(mgr.writers))
   119  	w := mgr.writers[testPlacementInstance.ID()].instanceWriter.(*writer)
   120  
   121  	w.queue.(*queue).writeFn = func([]byte) error {
   122  		time.Sleep(time.Second)
   123  		return nil
   124  	}
   125  	mgr.Unlock()
   126  
   127  	data := []byte("foo")
   128  	for i := 0; i < opts.InstanceQueueSize(); i++ {
   129  		require.NoError(t, w.queue.Enqueue(testNewBuffer(data)))
   130  	}
   131  
   132  	go mgr.RemoveInstances([]placement.Instance{testPlacementInstance})
   133  	require.True(t, clock.WaitUntil(func() bool {
   134  		mgr.Lock()
   135  		defer mgr.Unlock()
   136  		return len(mgr.writers) == 0
   137  	}, 3*time.Second))
   138  }
   139  
   140  func TestWriterManagerWriteUntimedClosed(t *testing.T) {
   141  	payload := payloadUnion{
   142  		payloadType: untimedType,
   143  		untimed: untimedPayload{
   144  			metric:    testCounter,
   145  			metadatas: testStagedMetadatas,
   146  		},
   147  	}
   148  	mgr := mustMakeInstanceWriterManager(testOptions())
   149  	mgr.Lock()
   150  	mgr.closed = true
   151  	mgr.Unlock()
   152  	err := mgr.Write(testPlacementInstance, 0, payload)
   153  	require.Equal(t, errInstanceWriterManagerClosed, err)
   154  }
   155  
   156  func TestWriterManagerWriteUntimedNoInstances(t *testing.T) {
   157  	payload := payloadUnion{
   158  		payloadType: untimedType,
   159  		untimed: untimedPayload{
   160  			metric:    testCounter,
   161  			metadatas: testStagedMetadatas,
   162  		},
   163  	}
   164  	mgr := mustMakeInstanceWriterManager(testOptions())
   165  	err := mgr.Write(testPlacementInstance, 0, payload)
   166  	require.Error(t, err)
   167  	require.NoError(t, mgr.Close())
   168  }
   169  
   170  func TestWriterManagerWriteUntimedSuccess(t *testing.T) {
   171  	ctrl := gomock.NewController(t)
   172  	defer ctrl.Finish()
   173  
   174  	var (
   175  		instances = []placement.Instance{
   176  			testPlacementInstance,
   177  			placement.NewInstance().
   178  				SetID("foo").
   179  				SetEndpoint("fooAddr"),
   180  		}
   181  		shardRes   uint32
   182  		payloadRes payloadUnion
   183  	)
   184  	writer := NewMockinstanceWriter(ctrl)
   185  	writer.EXPECT().QueueSize().AnyTimes()
   186  	writer.EXPECT().
   187  		Write(gomock.Any(), gomock.Any()).
   188  		DoAndReturn(func(
   189  			shard uint32,
   190  			payload payloadUnion,
   191  		) error {
   192  			shardRes = shard
   193  			payloadRes = payload
   194  			return nil
   195  		})
   196  	mgr := mustMakeInstanceWriterManager(testOptions())
   197  	mgr.Lock()
   198  	mgr.writers[instances[0].ID()] = &refCountedWriter{
   199  		refCount:       refCount{n: 1},
   200  		instanceWriter: writer,
   201  	}
   202  	mgr.Unlock()
   203  
   204  	payload := payloadUnion{
   205  		payloadType: untimedType,
   206  		untimed: untimedPayload{
   207  			metric:    testCounter,
   208  			metadatas: testStagedMetadatas,
   209  		},
   210  	}
   211  	require.NoError(t, mgr.Write(testPlacementInstance, 0, payload))
   212  	mgr.Lock()
   213  	assert.Equal(t, 1, len(mgr.writers))
   214  	mgr.Unlock()
   215  	require.Equal(t, uint32(0), shardRes)
   216  	require.Equal(t, untimedType, payloadRes.payloadType)
   217  	require.Equal(t, testCounter, payloadRes.untimed.metric)
   218  	require.Equal(t, testStagedMetadatas, payloadRes.untimed.metadatas)
   219  }
   220  
   221  func TestWriterManagerFlushClosed(t *testing.T) {
   222  	mgr := mustMakeInstanceWriterManager(testOptions())
   223  	mgr.closed = true
   224  	require.Equal(t, errInstanceWriterManagerClosed, mgr.Flush())
   225  }
   226  
   227  func TestWriterManagerFlushPartialError(t *testing.T) {
   228  	ctrl := gomock.NewController(t)
   229  	defer ctrl.Finish()
   230  
   231  	var (
   232  		numFlushes atomic.Int64
   233  		instances  = []placement.Instance{
   234  			testPlacementInstance,
   235  			placement.NewInstance().
   236  				SetID("foo").
   237  				SetEndpoint("fooAddr"),
   238  		}
   239  	)
   240  
   241  	writer1 := NewMockinstanceWriter(ctrl)
   242  	writer1.EXPECT().QueueSize().AnyTimes()
   243  	writer1.EXPECT().Write(gomock.Any(), gomock.Any())
   244  	writer1.EXPECT().
   245  		Flush().
   246  		DoAndReturn(func() error {
   247  			numFlushes.Inc()
   248  			return nil
   249  		})
   250  	errTestFlush := errors.New("test flush error")
   251  	writer2 := NewMockinstanceWriter(ctrl)
   252  	writer2.EXPECT().QueueSize().AnyTimes()
   253  	writer2.EXPECT().Write(gomock.Any(), gomock.Any())
   254  	writer2.EXPECT().
   255  		Flush().
   256  		DoAndReturn(func() error {
   257  			return errTestFlush
   258  		})
   259  	mgr := mustMakeInstanceWriterManager(testOptions())
   260  	mgr.Lock()
   261  	mgr.writers[instances[0].ID()] = &refCountedWriter{
   262  		refCount:       refCount{n: 1},
   263  		instanceWriter: writer1,
   264  	}
   265  	mgr.writers[instances[1].ID()] = &refCountedWriter{
   266  		refCount:       refCount{n: 1},
   267  		instanceWriter: writer2,
   268  	}
   269  	mgr.Unlock()
   270  	mgr.Write(instances[0], 0, payloadUnion{}) //nolint:errcheck
   271  	mgr.Write(instances[1], 0, payloadUnion{}) //nolint:errcheck
   272  	err := mgr.Flush()
   273  	require.Error(t, err)
   274  	require.True(t, strings.Contains(err.Error(), errTestFlush.Error()))
   275  	require.Equal(t, int64(1), numFlushes.Load())
   276  }
   277  
   278  func TestWriterManagerCloseAlreadyClosed(t *testing.T) {
   279  	mgr := mustMakeInstanceWriterManager(testOptions())
   280  	mgr.Lock()
   281  	mgr.closed = true
   282  	mgr.Unlock()
   283  	require.Equal(t, errInstanceWriterManagerClosed, mgr.Close())
   284  }
   285  
   286  func TestWriterManagerCloseSuccess(t *testing.T) {
   287  	// TODO: other tests don't clean up properly, and pool has no Shutdown method
   288  	defer goleak.VerifyNone(
   289  		t,
   290  		goleak.IgnoreCurrent(),
   291  		goleak.IgnoreTopFunction("github.com/m3db/m3/src/x/sync.(*pooledWorkerPool).spawnWorker.func1"),
   292  	)
   293  
   294  	mgr := mustMakeInstanceWriterManager(testOptions())
   295  
   296  	// Add instance list and close.
   297  	require.NoError(t, mgr.AddInstances([]placement.Instance{testPlacementInstance}))
   298  	require.NoError(t, mgr.Close())
   299  	mgr.Lock()
   300  	require.True(t, mgr.closed)
   301  	mgr.Unlock()
   302  
   303  	require.True(t, clock.WaitUntil(func() bool {
   304  		for _, w := range mgr.writers {
   305  			wr := w.instanceWriter.(*writer)
   306  			wr.Lock()
   307  			closed := wr.closed
   308  			wr.Unlock()
   309  
   310  			if !closed {
   311  				return false
   312  			}
   313  		}
   314  		return true
   315  	}, 3*time.Second))
   316  }
   317  
   318  func mustMakeInstanceWriterManager(opts Options) *writerManager {
   319  	wm, err := newInstanceWriterManager(opts)
   320  	if err != nil {
   321  		panic(err)
   322  	}
   323  
   324  	return wm.(*writerManager)
   325  }