You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							143 lines
						
					
					
						
							3.7 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							143 lines
						
					
					
						
							3.7 KiB
						
					
					
				
								package exclusive_locks
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"sync/atomic"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/wdclient"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								const (
							 | 
						|
									RenewInterval     = 4 * time.Second
							 | 
						|
									SafeRenewInterval = 3 * time.Second
							 | 
						|
									InitLockInterval  = 1 * time.Second
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								type ExclusiveLocker struct {
							 | 
						|
									token        int64
							 | 
						|
									lockTsNs     int64
							 | 
						|
									isLocked     atomic.Bool
							 | 
						|
									masterClient *wdclient.MasterClient
							 | 
						|
									lockName     string
							 | 
						|
									message      string
							 | 
						|
									clientName   string
							 | 
						|
									// Each lock has and only has one goroutine
							 | 
						|
									renewGoroutineRunning atomic.Bool
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func NewExclusiveLocker(masterClient *wdclient.MasterClient, lockName string) *ExclusiveLocker {
							 | 
						|
									return &ExclusiveLocker{
							 | 
						|
										masterClient: masterClient,
							 | 
						|
										lockName:     lockName,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (l *ExclusiveLocker) IsLocked() bool {
							 | 
						|
									return l.isLocked.Load()
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (l *ExclusiveLocker) GetToken() (token int64, lockTsNs int64) {
							 | 
						|
									for time.Unix(0, atomic.LoadInt64(&l.lockTsNs)).Add(SafeRenewInterval).Before(time.Now()) {
							 | 
						|
										// wait until now is within the safe lock period, no immediate renewal to change the token
							 | 
						|
										time.Sleep(100 * time.Millisecond)
							 | 
						|
									}
							 | 
						|
									return atomic.LoadInt64(&l.token), atomic.LoadInt64(&l.lockTsNs)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (l *ExclusiveLocker) RequestLock(clientName string) {
							 | 
						|
									if l.isLocked.Load() {
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									ctx, cancel := context.WithCancel(context.Background())
							 | 
						|
									defer cancel()
							 | 
						|
								
							 | 
						|
									// retry to get the lease
							 | 
						|
									for {
							 | 
						|
										if err := l.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
							 | 
						|
											resp, err := client.LeaseAdminToken(ctx, &master_pb.LeaseAdminTokenRequest{
							 | 
						|
												PreviousToken:    atomic.LoadInt64(&l.token),
							 | 
						|
												PreviousLockTime: atomic.LoadInt64(&l.lockTsNs),
							 | 
						|
												LockName:         l.lockName,
							 | 
						|
												ClientName:       clientName,
							 | 
						|
											})
							 | 
						|
											if err == nil {
							 | 
						|
												atomic.StoreInt64(&l.token, resp.Token)
							 | 
						|
												atomic.StoreInt64(&l.lockTsNs, resp.LockTsNs)
							 | 
						|
											}
							 | 
						|
											return err
							 | 
						|
										}); err != nil {
							 | 
						|
											println("lock:", err.Error())
							 | 
						|
											time.Sleep(InitLockInterval)
							 | 
						|
										} else {
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									l.isLocked.Store(true)
							 | 
						|
									l.clientName = clientName
							 | 
						|
								
							 | 
						|
									// Each lock has and only has one goroutine
							 | 
						|
									if l.renewGoroutineRunning.CompareAndSwap(false, true) {
							 | 
						|
										// start a goroutine to renew the lease
							 | 
						|
										go func() {
							 | 
						|
											ctx2, cancel2 := context.WithCancel(context.Background())
							 | 
						|
											defer cancel2()
							 | 
						|
								
							 | 
						|
											for {
							 | 
						|
												if l.isLocked.Load() {
							 | 
						|
													if err := l.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
							 | 
						|
														resp, err := client.LeaseAdminToken(ctx2, &master_pb.LeaseAdminTokenRequest{
							 | 
						|
															PreviousToken:    atomic.LoadInt64(&l.token),
							 | 
						|
															PreviousLockTime: atomic.LoadInt64(&l.lockTsNs),
							 | 
						|
															LockName:         l.lockName,
							 | 
						|
															ClientName:       l.clientName,
							 | 
						|
															Message:          l.message,
							 | 
						|
														})
							 | 
						|
														if err == nil {
							 | 
						|
															atomic.StoreInt64(&l.token, resp.Token)
							 | 
						|
															atomic.StoreInt64(&l.lockTsNs, resp.LockTsNs)
							 | 
						|
															// println("ts", l.lockTsNs, "token", l.token)
							 | 
						|
														}
							 | 
						|
														return err
							 | 
						|
													}); err != nil {
							 | 
						|
														glog.Errorf("failed to renew lock: %v", err)
							 | 
						|
														l.isLocked.Store(false)
							 | 
						|
														return
							 | 
						|
													} else {
							 | 
						|
														time.Sleep(RenewInterval)
							 | 
						|
													}
							 | 
						|
												} else {
							 | 
						|
													time.Sleep(RenewInterval)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										}()
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (l *ExclusiveLocker) ReleaseLock() {
							 | 
						|
									l.isLocked.Store(false)
							 | 
						|
									l.clientName = ""
							 | 
						|
								
							 | 
						|
									ctx, cancel := context.WithCancel(context.Background())
							 | 
						|
									defer cancel()
							 | 
						|
								
							 | 
						|
									l.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
							 | 
						|
										client.ReleaseAdminToken(ctx, &master_pb.ReleaseAdminTokenRequest{
							 | 
						|
											PreviousToken:    atomic.LoadInt64(&l.token),
							 | 
						|
											PreviousLockTime: atomic.LoadInt64(&l.lockTsNs),
							 | 
						|
											LockName:         l.lockName,
							 | 
						|
										})
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
									atomic.StoreInt64(&l.token, 0)
							 | 
						|
									atomic.StoreInt64(&l.lockTsNs, 0)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (l *ExclusiveLocker) SetMessage(message string) {
							 | 
						|
									l.message = message
							 | 
						|
								}
							 |