go.etcd.io/etcd@v3.3.27+incompatible/clientv3/leasing/txn.go (about) 1 // Copyright 2017 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package leasing 16 17 import ( 18 "context" 19 "strings" 20 21 v3 "github.com/coreos/etcd/clientv3" 22 v3pb "github.com/coreos/etcd/etcdserver/etcdserverpb" 23 ) 24 25 type txnLeasing struct { 26 v3.Txn 27 lkv *leasingKV 28 ctx context.Context 29 cs []v3.Cmp 30 opst []v3.Op 31 opse []v3.Op 32 } 33 34 func (txn *txnLeasing) If(cs ...v3.Cmp) v3.Txn { 35 txn.cs = append(txn.cs, cs...) 36 txn.Txn = txn.Txn.If(cs...) 37 return txn 38 } 39 40 func (txn *txnLeasing) Then(ops ...v3.Op) v3.Txn { 41 txn.opst = append(txn.opst, ops...) 42 txn.Txn = txn.Txn.Then(ops...) 43 return txn 44 } 45 46 func (txn *txnLeasing) Else(ops ...v3.Op) v3.Txn { 47 txn.opse = append(txn.opse, ops...) 48 txn.Txn = txn.Txn.Else(ops...) 49 return txn 50 } 51 52 func (txn *txnLeasing) Commit() (*v3.TxnResponse, error) { 53 if resp, err := txn.eval(); resp != nil || err != nil { 54 return resp, err 55 } 56 return txn.serverTxn() 57 } 58 59 func (txn *txnLeasing) eval() (*v3.TxnResponse, error) { 60 // TODO: wait on keys in comparisons 61 thenOps, elseOps := gatherOps(txn.opst), gatherOps(txn.opse) 62 ops := make([]v3.Op, 0, len(thenOps)+len(elseOps)) 63 ops = append(ops, thenOps...) 64 ops = append(ops, elseOps...) 65 66 for _, ch := range txn.lkv.leases.NotifyOps(ops) { 67 select { 68 case <-ch: 69 case <-txn.ctx.Done(): 70 return nil, txn.ctx.Err() 71 } 72 } 73 74 txn.lkv.leases.mu.RLock() 75 defer txn.lkv.leases.mu.RUnlock() 76 succeeded, ok := txn.lkv.leases.evalCmp(txn.cs) 77 if !ok || txn.lkv.leases.header == nil { 78 return nil, nil 79 } 80 if ops = txn.opst; !succeeded { 81 ops = txn.opse 82 } 83 84 resps, ok := txn.lkv.leases.evalOps(ops) 85 if !ok { 86 return nil, nil 87 } 88 return &v3.TxnResponse{Header: copyHeader(txn.lkv.leases.header), Succeeded: succeeded, Responses: resps}, nil 89 } 90 91 // fallback computes the ops to fetch all possible conflicting 92 // leasing keys for a list of ops. 93 func (txn *txnLeasing) fallback(ops []v3.Op) (fbOps []v3.Op) { 94 for _, op := range ops { 95 if op.IsGet() { 96 continue 97 } 98 lkey, lend := txn.lkv.pfx+string(op.KeyBytes()), "" 99 if len(op.RangeBytes()) > 0 { 100 lend = txn.lkv.pfx + string(op.RangeBytes()) 101 } 102 fbOps = append(fbOps, v3.OpGet(lkey, v3.WithRange(lend))) 103 } 104 return fbOps 105 } 106 107 func (txn *txnLeasing) guardKeys(ops []v3.Op) (cmps []v3.Cmp) { 108 seen := make(map[string]bool) 109 for _, op := range ops { 110 key := string(op.KeyBytes()) 111 if op.IsGet() || len(op.RangeBytes()) != 0 || seen[key] { 112 continue 113 } 114 rev := txn.lkv.leases.Rev(key) 115 cmps = append(cmps, v3.Compare(v3.CreateRevision(txn.lkv.pfx+key), "<", rev+1)) 116 seen[key] = true 117 } 118 return cmps 119 } 120 121 func (txn *txnLeasing) guardRanges(ops []v3.Op) (cmps []v3.Cmp, err error) { 122 for _, op := range ops { 123 if op.IsGet() || len(op.RangeBytes()) == 0 { 124 continue 125 } 126 127 key, end := string(op.KeyBytes()), string(op.RangeBytes()) 128 maxRevLK, err := txn.lkv.revokeRange(txn.ctx, key, end) 129 if err != nil { 130 return nil, err 131 } 132 133 opts := append(v3.WithLastRev(), v3.WithRange(end)) 134 getResp, err := txn.lkv.kv.Get(txn.ctx, key, opts...) 135 if err != nil { 136 return nil, err 137 } 138 maxModRev := int64(0) 139 if len(getResp.Kvs) > 0 { 140 maxModRev = getResp.Kvs[0].ModRevision 141 } 142 143 noKeyUpdate := v3.Compare(v3.ModRevision(key).WithRange(end), "<", maxModRev+1) 144 noLeaseUpdate := v3.Compare( 145 v3.CreateRevision(txn.lkv.pfx+key).WithRange(txn.lkv.pfx+end), 146 "<", 147 maxRevLK+1) 148 cmps = append(cmps, noKeyUpdate, noLeaseUpdate) 149 } 150 return cmps, nil 151 } 152 153 func (txn *txnLeasing) guard(ops []v3.Op) ([]v3.Cmp, error) { 154 cmps := txn.guardKeys(ops) 155 rangeCmps, err := txn.guardRanges(ops) 156 return append(cmps, rangeCmps...), err 157 } 158 159 func (txn *txnLeasing) commitToCache(txnResp *v3pb.TxnResponse, userTxn v3.Op) { 160 ops := gatherResponseOps(txnResp.Responses, []v3.Op{userTxn}) 161 txn.lkv.leases.mu.Lock() 162 for _, op := range ops { 163 key := string(op.KeyBytes()) 164 if op.IsDelete() && len(op.RangeBytes()) > 0 { 165 end := string(op.RangeBytes()) 166 for k := range txn.lkv.leases.entries { 167 if inRange(k, key, end) { 168 txn.lkv.leases.delete(k, txnResp.Header) 169 } 170 } 171 } else if op.IsDelete() { 172 txn.lkv.leases.delete(key, txnResp.Header) 173 } 174 if op.IsPut() { 175 txn.lkv.leases.Update(op.KeyBytes(), op.ValueBytes(), txnResp.Header) 176 } 177 } 178 txn.lkv.leases.mu.Unlock() 179 } 180 181 func (txn *txnLeasing) revokeFallback(fbResps []*v3pb.ResponseOp) error { 182 for _, resp := range fbResps { 183 _, err := txn.lkv.revokeLeaseKvs(txn.ctx, resp.GetResponseRange().Kvs) 184 if err != nil { 185 return err 186 } 187 } 188 return nil 189 } 190 191 func (txn *txnLeasing) serverTxn() (*v3.TxnResponse, error) { 192 if err := txn.lkv.waitSession(txn.ctx); err != nil { 193 return nil, err 194 } 195 196 userOps := gatherOps(append(txn.opst, txn.opse...)) 197 userTxn := v3.OpTxn(txn.cs, txn.opst, txn.opse) 198 fbOps := txn.fallback(userOps) 199 200 defer closeAll(txn.lkv.leases.LockWriteOps(userOps)) 201 for { 202 cmps, err := txn.guard(userOps) 203 if err != nil { 204 return nil, err 205 } 206 resp, err := txn.lkv.kv.Txn(txn.ctx).If(cmps...).Then(userTxn).Else(fbOps...).Commit() 207 if err != nil { 208 for _, cmp := range cmps { 209 txn.lkv.leases.Evict(strings.TrimPrefix(string(cmp.Key), txn.lkv.pfx)) 210 } 211 return nil, err 212 } 213 if resp.Succeeded { 214 txn.commitToCache((*v3pb.TxnResponse)(resp), userTxn) 215 userResp := resp.Responses[0].GetResponseTxn() 216 userResp.Header = resp.Header 217 return (*v3.TxnResponse)(userResp), nil 218 } 219 if err := txn.revokeFallback(resp.Responses); err != nil { 220 return nil, err 221 } 222 } 223 }