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 }