github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/dgraph/cmd/counter/increment_test.go (about)

     1  /*
     2   * Copyright 2018 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package counter
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math/rand"
    23  	"strings"
    24  	"sync"
    25  	"sync/atomic"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/dgraph-io/dgo"
    30  	"github.com/dgraph-io/dgo/protos/api"
    31  	"github.com/dgraph-io/dgraph/testutil"
    32  	"github.com/dgraph-io/dgraph/x"
    33  	"github.com/spf13/viper"
    34  	"github.com/stretchr/testify/require"
    35  	"google.golang.org/grpc/metadata"
    36  )
    37  
    38  const N = 10
    39  const pred = "counter"
    40  
    41  func incrementInLoop(t *testing.T, dg *dgo.Dgraph, M int) int {
    42  	conf := viper.New()
    43  	conf.Set("pred", "counter.val")
    44  
    45  	var max int
    46  	for i := 0; i < M; i++ {
    47  		cnt, err := process(dg, conf)
    48  		if err != nil {
    49  			if strings.Index(err.Error(), "Transaction has been aborted") >= 0 {
    50  				// pass
    51  			} else {
    52  				t.Logf("Error while incrementing: %v\n", err)
    53  			}
    54  		} else {
    55  			if cnt.Val > max {
    56  				max = cnt.Val
    57  			}
    58  		}
    59  	}
    60  	t.Logf("Last value written by increment in loop: %d", max)
    61  	return max
    62  }
    63  
    64  func increment(t *testing.T, dg *dgo.Dgraph) int {
    65  	var max int
    66  	var mu sync.Mutex
    67  	storeMax := func(a int) {
    68  		mu.Lock()
    69  		if max < a {
    70  			max = a
    71  		}
    72  		mu.Unlock()
    73  	}
    74  
    75  	var wg sync.WaitGroup
    76  	// N goroutines, process N times each goroutine.
    77  	for i := 0; i < N; i++ {
    78  		wg.Add(1)
    79  		go func() {
    80  			defer wg.Done()
    81  			max := incrementInLoop(t, dg, N)
    82  			storeMax(max)
    83  		}()
    84  	}
    85  	wg.Wait()
    86  	return max
    87  }
    88  
    89  func read(t *testing.T, dg *dgo.Dgraph, expected int) {
    90  	conf := viper.New()
    91  	conf.Set("pred", "counter.val")
    92  	conf.Set("ro", true)
    93  	cnt, err := process(dg, conf)
    94  	require.NoError(t, err)
    95  	ts := cnt.startTs
    96  	t.Logf("Readonly stage counter: %+v\n", cnt)
    97  
    98  	var wg sync.WaitGroup
    99  	for i := 0; i < N; i++ {
   100  		wg.Add(1)
   101  		go func() {
   102  			defer wg.Done()
   103  			for i := 0; i < N; i++ {
   104  				cnt, err := process(dg, conf)
   105  				if err != nil {
   106  					t.Logf("Error while reading: %v\n", err)
   107  				} else {
   108  					require.Equal(t, expected, cnt.Val)
   109  					require.True(t, cnt.startTs >= ts, "the timestamp should never decrease")
   110  				}
   111  			}
   112  		}()
   113  	}
   114  	wg.Wait()
   115  }
   116  
   117  func readBestEffort(t *testing.T, dg *dgo.Dgraph, pred string, M int) {
   118  	conf := viper.New()
   119  	conf.Set("pred", pred)
   120  	conf.Set("be", true)
   121  	var last int
   122  	for i := 0; i < M; i++ {
   123  		cnt, err := process(dg, conf)
   124  		if err != nil {
   125  			t.Errorf("Error while reading: %v", err)
   126  		} else {
   127  			if last > cnt.Val {
   128  				t.Errorf("Current %d < Last %d", cnt.Val, last)
   129  			}
   130  			last = cnt.Val
   131  		}
   132  	}
   133  	t.Logf("Last value read by best effort: %d", last)
   134  }
   135  
   136  func setup(t *testing.T) *dgo.Dgraph {
   137  	dg := testutil.DgraphClientWithGroot(testutil.SockAddr)
   138  	ctx := context.Background()
   139  	op := api.Operation{DropAll: true}
   140  
   141  	// The following piece of code shows how one can set metadata with
   142  	// auth-token, to allow Alter operation, if the server requires it.
   143  	md := metadata.New(nil)
   144  	md.Append("auth-token", "mrjn2")
   145  	ctx = metadata.NewOutgoingContext(ctx, md)
   146  	x.Check(dg.Alter(ctx, &op))
   147  
   148  	conf := viper.New()
   149  	conf.Set("pred", "counter.val")
   150  	cnt, err := process(dg, conf)
   151  	if err != nil {
   152  		t.Logf("Error while reading: %v\n", err)
   153  	} else {
   154  		t.Logf("Initial value: %d\n", cnt.Val)
   155  	}
   156  
   157  	return dg
   158  }
   159  
   160  func TestIncrement(t *testing.T) {
   161  	dg := setup(t)
   162  	val := increment(t, dg)
   163  	t.Logf("Increment stage done. Got value: %d\n", val)
   164  	read(t, dg, val)
   165  	t.Logf("Read stage done with value: %d\n", val)
   166  	val = increment(t, dg)
   167  	t.Logf("Increment stage done. Got value: %d\n", val)
   168  	read(t, dg, val)
   169  	t.Logf("Read stage done with value: %d\n", val)
   170  }
   171  
   172  func TestBestEffort(t *testing.T) {
   173  	dg := setup(t)
   174  
   175  	var done int32
   176  	var wg sync.WaitGroup
   177  	wg.Add(2)
   178  	go func() {
   179  		defer wg.Done()
   180  		for i := 0; ; i++ {
   181  			incrementInLoop(t, dg, 5)
   182  			if atomic.LoadInt32(&done) > 0 {
   183  				return
   184  			}
   185  		}
   186  	}()
   187  	go func() {
   188  		defer wg.Done()
   189  		time.Sleep(time.Second)
   190  		readBestEffort(t, dg, "counter.val", 1000)
   191  		atomic.AddInt32(&done, 1)
   192  	}()
   193  	wg.Wait()
   194  	t.Logf("Write/Best-Effort read stage OK.")
   195  }
   196  
   197  func TestBestEffortOnly(t *testing.T) {
   198  	dg := setup(t)
   199  	readBestEffort(t, dg, fmt.Sprintf("counter.val.%d", rand.Int()), 1)
   200  	time.Sleep(time.Second)
   201  
   202  	doneCh := make(chan struct{})
   203  	go func() {
   204  		for i := 0; i < 10; i++ {
   205  			readBestEffort(t, dg, fmt.Sprintf("counter.val.%d", rand.Int()), 1)
   206  		}
   207  		doneCh <- struct{}{}
   208  	}()
   209  
   210  	timer := time.NewTimer(15 * time.Second)
   211  	defer timer.Stop()
   212  
   213  	select {
   214  	case <-timer.C:
   215  		t.FailNow()
   216  	case <-doneCh:
   217  	}
   218  	t.Logf("Best-Effort only reads with multiple preds OK.")
   219  }
   220  
   221  func TestBestEffortTs(t *testing.T) {
   222  	dg := setup(t)
   223  	pred := "counter.val"
   224  	incrementInLoop(t, dg, 1)
   225  	readBestEffort(t, dg, pred, 1)
   226  	txn := dg.NewReadOnlyTxn().BestEffort()
   227  	_, err := queryCounter(txn, pred)
   228  	require.NoError(t, err)
   229  
   230  	incrementInLoop(t, dg, 1)        // Increment the MaxAssigned ts at Alpha.
   231  	_, err = queryCounter(txn, pred) // The timestamp here shouldn't change.
   232  	require.NoError(t, err)
   233  }