package publock import ( "context" "sync" "time" "github.com/google/uuid" "gitlink.org.cn/cloudream/common/pkgs/future" "gitlink.org.cn/cloudream/common/pkgs/logger" "gitlink.org.cn/cloudream/common/utils/lo2" "gitlink.org.cn/cloudream/common/utils/serder" "gitlink.org.cn/cloudream/jcs-pub/client/internal/cluster" "gitlink.org.cn/cloudream/jcs-pub/client/internal/publock/types" "gitlink.org.cn/cloudream/jcs-pub/common/ecode" clirpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/client" ) type AcquireOptionFn func(opt *types.AcquireOption) func WithTimeout(timeout time.Duration) AcquireOptionFn { return func(opt *types.AcquireOption) { opt.Timeout = timeout } } func WithReason(reason string) AcquireOptionFn { return func(opt *types.AcquireOption) { opt.Reason = reason } } type PubLock struct { core *Core clster *cluster.Cluster lock sync.Mutex acquirings map[types.RequestID]*svcAcquiring acquired []types.RequestID releasing map[types.RequestID]*svcReleasing } func New(cfg Config, clster *cluster.Cluster) *PubLock { return &PubLock{ core: NewCore(cfg, clster), clster: clster, acquirings: make(map[types.RequestID]*svcAcquiring), releasing: make(map[types.RequestID]*svcReleasing), } } func (s *PubLock) BeginReentrant() *Reentrant { r := &Reentrant{ p: s, } r.T = r return r } func (p *PubLock) BeginMutex() *MutexBuilder { m := &MutexBuilder{ pub: p, } m.T = m return m } func (p *PubLock) Core() *Core { return p.core } func (p *PubLock) Start() { log := logger.WithField("Mod", "PubLock") go func() { ch := p.core.EventChan() evt := ch.Receive() ticker := time.NewTicker(time.Second) defer ticker.Stop() loop: for { select { case <-ticker.C: p.lock.Lock() reqIDs := make([]types.RequestID, len(p.acquired)) copy(reqIDs, p.acquired) p.lock.Unlock() if len(reqIDs) == 0 { continue } cmd := Renew{ IDs: reqIDs, } if p.clster == nil { p.core.Apply(&cmd) } else { data, err := serder.ObjectToJSONEx(cmd) if err != nil { log.Warnf("cmd %T to json: %v", cmd, err) continue } _, cerr := p.clster.MasterClient().ClusterApplyLog(context.Background(), &clirpc.ClusterApplyLog{ FSMID: p.core.FSM().ID(), Data: data, Timeout: time.Second * 3, }) if cerr != nil { log.Errorf("apply renew: %v", cerr) } } case ret := <-evt.Chan(): if ret.Err != nil { break loop } p.lock.Lock() switch e := ret.Value.(type) { case *AcquireResult: a, ok := p.acquirings[e.Raw.ID] if !ok { break } if e.Error == nil { a.Callback.SetVoid() p.acquired = append(p.acquired, e.Raw.ID) } else { a.Callback.SetError(e.Error) } delete(p.acquirings, e.Raw.ID) case *Released: r, ok := p.releasing[e.ID] if !ok { break } r.Callback.SetVoid() p.acquired = lo2.Remove(p.acquired, e.ID) delete(p.releasing, e.ID) } p.lock.Unlock() evt = ch.Receive() } } }() p.core.Start() } func (p *PubLock) Stop() { p.core.Stop() } func (p *PubLock) Acquire(req types.LockRequest, opt types.AcquireOption) (LockedRequest, *ecode.CodeError) { p.lock.Lock() cmd := Acquire{ ID: types.RequestID(uuid.NewString()), Request: req, Timeout: opt.Timeout, Reason: opt.Reason, } ac := &svcAcquiring{ RequestID: cmd.ID, Request: cmd.Request, Callback: future.NewSetVoid(), } p.acquirings[cmd.ID] = ac p.lock.Unlock() if p.clster == nil { p.core.Apply(&cmd) } else { data, err := serder.ObjectToJSONEx(cmd) if err != nil { return LockedRequest{}, ecode.Newf(ecode.OperationFailed, "cmd %T to json: %v", cmd, err) } _, cerr := p.clster.MasterClient().ClusterApplyLog(context.Background(), &clirpc.ClusterApplyLog{ FSMID: p.core.FSM().ID(), Data: data, Timeout: opt.Timeout, }) if cerr != nil { return LockedRequest{}, ecode.New(ecode.ErrorCode(cerr.Code), cerr.Message) } } err := ac.Callback.Wait(context.Background()) if err != nil { return LockedRequest{}, ecode.Newf(ecode.OperationFailed, "wait acquire: %v", err) } return LockedRequest{ Req: req, ReqID: ac.RequestID, }, nil } func (p *PubLock) Release(reqID types.RequestID) { log := logger.WithField("Mod", "PubLock") p.lock.Lock() cmd := Release{ ID: reqID, } r := &svcReleasing{ RequestID: cmd.ID, Callback: future.NewSetVoid(), } p.releasing[cmd.ID] = r p.lock.Unlock() if p.clster == nil { p.core.Apply(&cmd) } else { data, err := serder.ObjectToJSONEx(cmd) if err != nil { log.Warnf("cmd %T to json: %v", cmd, err) return } _, cerr := p.clster.MasterClient().ClusterApplyLog(context.Background(), &clirpc.ClusterApplyLog{ FSMID: p.core.FSM().ID(), Data: data, Timeout: 0, }) if cerr != nil { log.Errorf("apply release: %v", cerr) } } err := r.Callback.Wait(context.Background()) if err != nil { log.Errorf("wait release: %v", err) } else { log.Tracef("unlock %v", reqID) } } type svcAcquiring struct { RequestID types.RequestID Request types.LockRequest Callback *future.SetVoidFuture } type svcReleasing struct { RequestID types.RequestID Callback *future.SetVoidFuture }