github.com/erda-project/erda-infra@v1.0.9/providers/etcd-mutex/mutex.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 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 mutex 16 17 import ( 18 "context" 19 "errors" 20 "path/filepath" 21 "reflect" 22 "sync" 23 "time" 24 25 "github.com/coreos/etcd/clientv3" 26 "github.com/coreos/etcd/clientv3/concurrency" 27 "github.com/erda-project/erda-infra/base/logs" 28 "github.com/erda-project/erda-infra/base/servicehub" 29 "github.com/erda-project/erda-infra/providers/etcd" 30 ) 31 32 // Mutex . 33 type Mutex interface { 34 Lock(ctx context.Context) error 35 Unlock(ctx context.Context) error 36 Close() error 37 } 38 39 // Interface . 40 type Interface interface { 41 NewWithTTL(ctx context.Context, key string, ttl time.Duration) (Mutex, error) 42 New(ctx context.Context, key string) (Mutex, error) 43 } 44 45 // ErrClosed mutex already closed 46 var ErrClosed = errors.New("mutex closed") 47 48 var mutexType = reflect.TypeOf((*Mutex)(nil)).Elem() 49 50 type config struct { 51 RootPath string `file:"root_path"` 52 DefaultKey string `file:"default_key"` 53 } 54 55 type provider struct { 56 Cfg *config 57 Log logs.Logger 58 etcd etcd.Interface 59 instances map[string]Mutex 60 inProcMutex *inProcMutex 61 } 62 63 // Init . 64 func (p *provider) Init(ctx servicehub.Context) error { 65 p.etcd = ctx.Service("etcd").(etcd.Interface) 66 p.Cfg.RootPath = filepath.Clean("/" + p.Cfg.RootPath) 67 return nil 68 } 69 70 // NewWithTTL . 71 func (p *provider) NewWithTTL(ctx context.Context, key string, ttl time.Duration) (Mutex, error) { 72 ctx, cancel := context.WithCancel(ctx) 73 opts := []concurrency.SessionOption{concurrency.WithContext(ctx)} 74 seconds := int(ttl.Seconds()) 75 if seconds > 0 { 76 opts = append(opts, concurrency.WithTTL(seconds)) 77 } 78 return &etcdMutex{ 79 log: p.Log, 80 key: filepath.Clean(filepath.Join(p.Cfg.RootPath, key)), 81 client: p.etcd.Client(), 82 opts: opts, 83 inProcLock: make(chan struct{}, 1), 84 ctx: ctx, 85 cancel: cancel, 86 }, nil 87 } 88 89 // New . 90 func (p *provider) New(ctx context.Context, key string) (Mutex, error) { 91 return p.NewWithTTL(ctx, key, time.Duration(0)) 92 } 93 94 // Provide . 95 func (p *provider) Provide(ctx servicehub.DependencyContext, args ...interface{}) interface{} { 96 if ctx.Type() == mutexType { 97 key := ctx.Tags().Get("mutex-key") 98 if len(key) <= 0 { 99 key = p.Cfg.DefaultKey 100 } 101 if len(key) <= 0 { 102 p.Log.Debugf("in-proc mutex for provider %q", ctx.Caller()) 103 return p.inProcMutex 104 } 105 m, err := p.New(context.Background(), key) 106 if err != nil { 107 p.Log.Errorf("fail to create mutex for key: %q", key) 108 } 109 return m 110 } 111 return p 112 } 113 114 type etcdMutex struct { 115 log logs.Logger 116 key string 117 client *clientv3.Client 118 opts []concurrency.SessionOption 119 ctx context.Context 120 cancel context.CancelFunc 121 122 lock sync.Mutex 123 s *concurrency.Session 124 mu *concurrency.Mutex 125 inProcLock chan struct{} 126 } 127 128 func (m *etcdMutex) resetSession() (*concurrency.Mutex, error) { 129 m.close() 130 s, mu, err := m.newSession() 131 if err != nil { 132 return nil, err 133 } 134 m.s, m.mu = s, mu 135 return mu, nil 136 } 137 138 func (m *etcdMutex) newSession() (*concurrency.Session, *concurrency.Mutex, error) { 139 session, err := concurrency.NewSession(m.client, m.opts...) 140 if err != nil { 141 m.log.Debugf("failed to new session for key %q: %s", m.key, err) 142 return nil, nil, err 143 } 144 m.log.Debugf("new session for key %q", m.key) 145 return session, concurrency.NewMutex(session, m.key), nil 146 } 147 148 func (m *etcdMutex) Lock(ctx context.Context) (err error) { 149 d := time.Second 150 sleep := func() bool { 151 select { 152 case <-time.After(d): 153 case <-ctx.Done(): 154 return false 155 } 156 if d < 8*time.Second { 157 d = d * 2 158 } 159 return true 160 } 161 162 select { 163 case m.inProcLock <- struct{}{}: 164 case <-m.ctx.Done(): 165 return ErrClosed 166 case <-ctx.Done(): 167 return context.Canceled 168 } 169 170 for { 171 m.lock.Lock() 172 select { 173 case <-m.ctx.Done(): 174 m.lock.Unlock() 175 return ErrClosed 176 case <-ctx.Done(): 177 m.lock.Unlock() 178 return context.Canceled 179 default: 180 } 181 mu := m.mu 182 if err != nil || mu == nil { 183 mu, err = m.resetSession() 184 if err != nil { 185 m.lock.Unlock() 186 if errors.Is(err, context.Canceled) { 187 return err 188 } 189 sleep() 190 continue 191 } 192 } 193 m.lock.Unlock() 194 195 err = mu.Lock(ctx) 196 if err != nil { 197 m.log.Errorf("failed to lock key %q: %s", m.key, err) 198 if errors.Is(err, context.Canceled) { 199 return err 200 } 201 continue 202 } 203 m.log.Debugf("locked key %q", m.key) 204 return nil 205 } 206 } 207 208 func (m *etcdMutex) Unlock(ctx context.Context) (err error) { 209 select { 210 case <-m.inProcLock: 211 case <-m.ctx.Done(): 212 return ErrClosed 213 case <-ctx.Done(): 214 return context.Canceled 215 } 216 217 m.lock.Lock() 218 mu := m.mu 219 if mu != nil { 220 err = m.mu.Unlock(ctx) 221 } 222 m.lock.Unlock() 223 224 if err != nil { 225 m.log.Errorf("failed to unlock key %q: %s", m.key, err) 226 return err 227 } 228 m.log.Debugf("unlocked key %q", m.key) 229 return err 230 } 231 232 func (m *etcdMutex) Close() error { 233 m.lock.Lock() 234 select { 235 case <-m.ctx.Done(): 236 m.lock.Unlock() 237 return nil 238 default: 239 m.cancel() 240 } 241 err := m.close() 242 if errors.Is(err, context.Canceled) { 243 err = nil 244 } 245 m.lock.Unlock() 246 return err 247 } 248 249 func (m *etcdMutex) close() (err error) { 250 if m.s != nil { 251 err = m.s.Close() 252 m.s, m.mu = nil, nil 253 } 254 return err 255 } 256 257 type inProcMutex struct { 258 lock chan struct{} 259 } 260 261 func (m *inProcMutex) Lock(ctx context.Context) error { 262 select { 263 case m.lock <- struct{}{}: 264 case <-ctx.Done(): 265 return context.Canceled 266 } 267 return nil 268 } 269 270 func (m *inProcMutex) Unlock(ctx context.Context) error { 271 select { 272 case <-m.lock: 273 case <-ctx.Done(): 274 return context.Canceled 275 } 276 return nil 277 } 278 279 func (m *inProcMutex) Close() error { return nil } 280 281 func init() { 282 servicehub.Register("etcd-mutex", &servicehub.Spec{ 283 Services: []string{"etcd-mutex"}, 284 Types: []reflect.Type{ 285 reflect.TypeOf((*Interface)(nil)).Elem(), 286 mutexType, 287 }, 288 Dependencies: []string{"etcd"}, 289 Description: "distributed lock implemented by etcd", 290 ConfigFunc: func() interface{} { return &config{} }, 291 Creator: func() servicehub.Provider { 292 return &provider{ 293 instances: make(map[string]Mutex), 294 inProcMutex: &inProcMutex{lock: make(chan struct{}, 1)}, 295 } 296 }, 297 }) 298 }