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 | |
| }
 |