123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package weed_server
- import (
- "context"
- "fmt"
- "math/rand"
- "sync"
- "time"
- "github.com/chrislusf/seaweedfs/weed/pb/master_pb"
- )
- /*
- How exclusive lock works?
- -----------
- Shell
- ------
- When shell lock,
- * lease an admin token (lockTime, token)
- * start a goroutine to renew the admin token periodically
- When shell unlock
- * stop the renewal goroutine
- * sends a release lock request
- Master
- ------
- Master maintains:
- * randomNumber
- * lastLockTime
- When master receives the lease/renew request from shell
- If lastLockTime still fresh {
- if is a renew and token is valid {
- // for renew
- generate the randomNumber => token
- return
- }
- refuse
- return
- } else {
- // for fresh lease request
- generate the randomNumber => token
- return
- }
- When master receives the release lock request from shell
- set the lastLockTime to zero
- The volume server does not need to verify.
- This makes the lock/unlock optional, similar to what golang code usually does.
- */
- const (
- LockDuration = 10 * time.Second
- )
- type AdminLock struct {
- accessSecret int64
- accessLockTime time.Time
- }
- type AdminLocks struct {
- locks map[string]*AdminLock
- sync.RWMutex
- }
- func NewAdminLocks() *AdminLocks {
- return &AdminLocks{
- locks: make(map[string]*AdminLock),
- }
- }
- func (locks *AdminLocks) isLocked(lockName string) bool {
- locks.RLock()
- defer locks.RUnlock()
- adminLock, found := locks.locks[lockName]
- if !found {
- return false
- }
- return adminLock.accessLockTime.Add(LockDuration).After(time.Now())
- }
- func (locks *AdminLocks) isValidToken(lockName string, ts time.Time, token int64) bool {
- locks.RLock()
- defer locks.RUnlock()
- adminLock, found := locks.locks[lockName]
- if !found {
- return false
- }
- return adminLock.accessLockTime.Equal(ts) && adminLock.accessSecret == token
- }
- func (locks *AdminLocks) generateToken(lockName string) (ts time.Time, token int64) {
- locks.Lock()
- defer locks.Unlock()
- lock := &AdminLock{
- accessSecret: rand.Int63(),
- accessLockTime: time.Now(),
- }
- locks.locks[lockName] = lock
- return lock.accessLockTime, lock.accessSecret
- }
- func (locks *AdminLocks) deleteLock(lockName string) {
- locks.Lock()
- defer locks.Unlock()
- delete(locks.locks, lockName)
- }
- func (ms *MasterServer) LeaseAdminToken(ctx context.Context, req *master_pb.LeaseAdminTokenRequest) (*master_pb.LeaseAdminTokenResponse, error) {
- resp := &master_pb.LeaseAdminTokenResponse{}
- if ms.adminLocks.isLocked(req.LockName) {
- if req.PreviousToken != 0 && ms.adminLocks.isValidToken(req.LockName, time.Unix(0, req.PreviousLockTime), req.PreviousToken) {
- // for renew
- ts, token := ms.adminLocks.generateToken(req.LockName)
- resp.Token, resp.LockTsNs = token, ts.UnixNano()
- return resp, nil
- }
- // refuse since still locked
- return resp, fmt.Errorf("already locked")
- }
- // for fresh lease request
- ts, token := ms.adminLocks.generateToken(req.LockName)
- resp.Token, resp.LockTsNs = token, ts.UnixNano()
- return resp, nil
- }
- func (ms *MasterServer) ReleaseAdminToken(ctx context.Context, req *master_pb.ReleaseAdminTokenRequest) (*master_pb.ReleaseAdminTokenResponse, error) {
- resp := &master_pb.ReleaseAdminTokenResponse{}
- if ms.adminLocks.isValidToken(req.LockName, time.Unix(0, req.PreviousLockTime), req.PreviousToken) {
- ms.adminLocks.deleteLock(req.LockName)
- }
- return resp, nil
- }
|