go.etcd.io/etcd@v3.3.27+incompatible/contrib/recipes/double_barrier.go (about) 1 // Copyright 2016 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 recipe 16 17 import ( 18 "context" 19 20 "github.com/coreos/etcd/clientv3" 21 "github.com/coreos/etcd/clientv3/concurrency" 22 "github.com/coreos/etcd/mvcc/mvccpb" 23 ) 24 25 // DoubleBarrier blocks processes on Enter until an expected count enters, then 26 // blocks again on Leave until all processes have left. 27 type DoubleBarrier struct { 28 s *concurrency.Session 29 ctx context.Context 30 31 key string // key for the collective barrier 32 count int 33 myKey *EphemeralKV // current key for this process on the barrier 34 } 35 36 func NewDoubleBarrier(s *concurrency.Session, key string, count int) *DoubleBarrier { 37 return &DoubleBarrier{ 38 s: s, 39 ctx: context.TODO(), 40 key: key, 41 count: count, 42 } 43 } 44 45 // Enter waits for "count" processes to enter the barrier then returns 46 func (b *DoubleBarrier) Enter() error { 47 client := b.s.Client() 48 ek, err := newUniqueEphemeralKey(b.s, b.key+"/waiters") 49 if err != nil { 50 return err 51 } 52 b.myKey = ek 53 54 resp, err := client.Get(b.ctx, b.key+"/waiters", clientv3.WithPrefix()) 55 if err != nil { 56 return err 57 } 58 59 if len(resp.Kvs) > b.count { 60 return ErrTooManyClients 61 } 62 63 if len(resp.Kvs) == b.count { 64 // unblock waiters 65 _, err = client.Put(b.ctx, b.key+"/ready", "") 66 return err 67 } 68 69 _, err = WaitEvents( 70 client, 71 b.key+"/ready", 72 ek.Revision(), 73 []mvccpb.Event_EventType{mvccpb.PUT}) 74 return err 75 } 76 77 // Leave waits for "count" processes to leave the barrier then returns 78 func (b *DoubleBarrier) Leave() error { 79 client := b.s.Client() 80 resp, err := client.Get(b.ctx, b.key+"/waiters", clientv3.WithPrefix()) 81 if err != nil { 82 return err 83 } 84 if len(resp.Kvs) == 0 { 85 return nil 86 } 87 88 lowest, highest := resp.Kvs[0], resp.Kvs[0] 89 for _, k := range resp.Kvs { 90 if k.ModRevision < lowest.ModRevision { 91 lowest = k 92 } 93 if k.ModRevision > highest.ModRevision { 94 highest = k 95 } 96 } 97 isLowest := string(lowest.Key) == b.myKey.Key() 98 99 if len(resp.Kvs) == 1 { 100 // this is the only node in the barrier; finish up 101 if _, err = client.Delete(b.ctx, b.key+"/ready"); err != nil { 102 return err 103 } 104 return b.myKey.Delete() 105 } 106 107 // this ensures that if a process fails, the ephemeral lease will be 108 // revoked, its barrier key is removed, and the barrier can resume 109 110 // lowest process in node => wait on highest process 111 if isLowest { 112 _, err = WaitEvents( 113 client, 114 string(highest.Key), 115 highest.ModRevision, 116 []mvccpb.Event_EventType{mvccpb.DELETE}) 117 if err != nil { 118 return err 119 } 120 return b.Leave() 121 } 122 123 // delete self and wait on lowest process 124 if err = b.myKey.Delete(); err != nil { 125 return err 126 } 127 128 key := string(lowest.Key) 129 _, err = WaitEvents( 130 client, 131 key, 132 lowest.ModRevision, 133 []mvccpb.Event_EventType{mvccpb.DELETE}) 134 if err != nil { 135 return err 136 } 137 return b.Leave() 138 }