2025-03-25 17:00:09 +08:00
package distributedlock
2025-02-28 16:00:16 +08:00
import (
2025-03-24 16:37:43 +08:00
"context"
2025-03-05 16:42:59 +08:00
"errors"
"fmt"
2025-02-28 16:00:16 +08:00
"strings"
2025-03-11 15:35:15 +08:00
"sync"
2025-02-28 16:00:16 +08:00
"time"
2025-03-05 16:42:59 +08:00
"modelRT/distributedlock/constant"
"modelRT/distributedlock/luascript"
"modelRT/logger"
2025-03-25 17:00:09 +08:00
uuid "github.com/gofrs/uuid"
2025-03-24 16:37:43 +08:00
"github.com/redis/go-redis/v9"
2025-02-28 16:00:16 +08:00
"go.uber.org/zap"
)
2025-03-06 16:35:36 +08:00
type RedissionRWLocker struct {
2025-02-28 16:00:16 +08:00
redissionLocker
2025-04-03 17:22:40 +08:00
writeWaitChanKey string
readWaitChanKey string
2025-03-07 16:16:26 +08:00
rwTokenTimeoutPrefix string
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) RLock ( ctx context . Context , timeout ... time . Duration ) error {
result := rl . tryRLock ( ctx ) . ( * constant . RedisResult )
2025-03-06 16:35:36 +08:00
if result . Code == constant . UnknownInternalError {
rl . logger . Error ( result . OutputResultMessage ( ) )
return fmt . Errorf ( "get read lock failed:%w" , result )
2025-02-28 16:00:16 +08:00
}
2025-03-12 16:24:28 +08:00
if result . Code == constant . LockSuccess {
if rl . needRefresh {
2025-04-07 16:49:06 +08:00
rl . refreshOnce . Do ( func ( ) {
if rl . refreshExitChan == nil {
rl . refreshExitChan = make ( chan struct { } )
}
2025-03-12 16:24:28 +08:00
// async refresh lock timeout unitl receive exit singal
2025-03-24 16:37:43 +08:00
go rl . refreshLockTimeout ( ctx )
2025-03-12 16:24:28 +08:00
} )
}
2025-04-02 16:47:51 +08:00
rl . logger . Info ( "success get the read lock by key and token" , zap . String ( "key" , rl . key ) , zap . String ( "token" , rl . token ) )
2025-03-05 16:42:59 +08:00
return nil
2025-02-28 16:00:16 +08:00
}
if len ( timeout ) > 0 && timeout [ 0 ] > 0 {
2025-04-07 16:49:06 +08:00
if rl . subExitChan == nil {
rl . subExitChan = make ( chan struct { } )
}
subMsgChan := make ( chan struct { } , 1 )
sub := rl . client . Subscribe ( ctx , rl . readWaitChanKey )
go rl . subscribeLock ( sub , subMsgChan )
2025-03-06 16:35:36 +08:00
acquireTimer := time . NewTimer ( timeout [ 0 ] )
2025-03-05 16:42:59 +08:00
for {
2025-02-28 16:00:16 +08:00
select {
2025-04-07 16:49:06 +08:00
case _ , ok := <- subMsgChan :
2025-02-28 16:00:16 +08:00
if ! ok {
2025-03-05 16:42:59 +08:00
err := errors . New ( "failed to read the read lock waiting for for the channel message" )
rl . logger . Error ( "failed to read the read lock waiting for for the channel message" )
return err
2025-02-28 16:00:16 +08:00
}
2025-04-07 16:49:06 +08:00
result := rl . tryRLock ( ctx ) . ( * constant . RedisResult )
if ( result . Code == constant . RLockFailureWithWLockOccupancy ) || ( result . Code == constant . UnknownInternalError ) {
rl . logger . Info ( result . OutputResultMessage ( ) )
2025-03-05 16:42:59 +08:00
continue
2025-02-28 16:00:16 +08:00
}
2025-04-07 16:49:06 +08:00
if result . Code == constant . LockSuccess {
rl . logger . Info ( result . OutputResultMessage ( ) )
rl . closeSub ( sub , rl . subExitChan )
2025-03-05 16:42:59 +08:00
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-05 16:42:59 +08:00
case <- acquireTimer . C :
rl . logger . Info ( "the waiting time for obtaining the read lock operation has timed out" )
2025-04-07 16:49:06 +08:00
rl . closeSub ( sub , rl . subExitChan )
// after acquire lock timeout,notice the sub channel to close
return constant . AcquireTimeoutErr
2025-02-28 16:00:16 +08:00
}
}
}
2025-03-07 16:16:26 +08:00
return fmt . Errorf ( "lock the redis read lock failed:%w" , result )
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) tryRLock ( ctx context . Context ) error {
2025-03-05 16:42:59 +08:00
lockType := constant . LockType
2025-03-06 16:35:36 +08:00
2025-03-24 16:37:43 +08:00
res := rl . client . Eval ( ctx , luascript . RLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix } , rl . lockLeaseTime , rl . token )
2025-03-06 16:35:36 +08:00
val , err := res . Int ( )
2025-02-28 16:00:16 +08:00
if err != redis . Nil && err != nil {
2025-03-06 16:35:36 +08:00
return constant . NewRedisResult ( constant . UnknownInternalError , lockType , err . Error ( ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
return constant . NewRedisResult ( constant . RedisCode ( val ) , lockType , "" )
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) refreshLockTimeout ( ctx context . Context ) {
2025-03-07 16:16:26 +08:00
rl . logger . Info ( "lock refresh by key and token" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
2025-03-06 16:35:36 +08:00
lockTime := time . Duration ( rl . lockLeaseTime / 3 ) * time . Second
2025-02-28 16:00:16 +08:00
timer := time . NewTimer ( lockTime )
defer timer . Stop ( )
2025-03-06 16:35:36 +08:00
2025-02-28 16:00:16 +08:00
for {
select {
case <- timer . C :
2025-03-06 16:35:36 +08:00
// extend key lease time
2025-03-24 16:37:43 +08:00
res := rl . client . Eval ( ctx , luascript . RefreshRWLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix } , rl . lockLeaseTime , rl . token )
2025-02-28 16:00:16 +08:00
val , err := res . Int ( )
2025-03-06 16:35:36 +08:00
if err != redis . Nil && err != nil {
2025-03-07 16:16:26 +08:00
rl . logger . Info ( "lock refresh failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) , zap . Error ( err ) )
2025-03-06 16:35:36 +08:00
return
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if constant . RedisCode ( val ) == constant . RefreshLockFailure {
2025-03-11 15:35:15 +08:00
rl . logger . Error ( "lock refreash failed,can not find the read lock by key and token" , zap . String ( "rwTokenPrefix" , rl . rwTokenTimeoutPrefix ) , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
return
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if constant . RedisCode ( val ) == constant . RefreshLockSuccess {
2025-03-07 16:16:26 +08:00
rl . logger . Info ( "lock refresh success by key and token" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
2025-03-06 16:35:36 +08:00
}
timer . Reset ( lockTime )
2025-04-07 16:49:06 +08:00
case <- rl . refreshExitChan :
2025-03-12 16:24:28 +08:00
return
2025-02-28 16:00:16 +08:00
}
}
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) UnRLock ( ctx context . Context ) error {
2025-03-12 16:24:28 +08:00
rl . logger . Info ( "unlock RLock by key and token" , zap . String ( "key" , rl . key ) , zap . String ( "token" , rl . token ) )
2025-04-03 17:22:40 +08:00
res := rl . client . Eval ( ctx , luascript . UnRLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix , rl . writeWaitChanKey } , unlockMessage , rl . token )
2025-03-06 16:35:36 +08:00
val , err := res . Int ( )
2025-02-28 16:00:16 +08:00
if err != redis . Nil && err != nil {
2025-03-06 16:35:36 +08:00
rl . logger . Info ( "unlock read lock failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) , zap . Error ( err ) )
2025-03-13 16:51:50 +08:00
return fmt . Errorf ( "unlock read lock failed:%w" , constant . NewRedisResult ( constant . UnknownInternalError , constant . UnRLockType , err . Error ( ) ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if ( constant . RedisCode ( val ) == constant . UnLockSuccess ) || ( constant . RedisCode ( val ) == constant . UnRLockSuccess ) {
2025-03-13 16:51:50 +08:00
if rl . needRefresh && ( constant . RedisCode ( val ) == constant . UnLockSuccess ) {
2025-03-06 16:35:36 +08:00
rl . cancelRefreshLockTime ( )
}
rl . logger . Info ( "unlock read lock success" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if constant . RedisCode ( val ) == constant . UnRLockFailureWithWLockOccupancy {
rl . logger . Info ( "unlock read lock failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
2025-03-13 16:51:50 +08:00
return fmt . Errorf ( "unlock read lock failed:%w" , constant . NewRedisResult ( constant . UnRLockFailureWithWLockOccupancy , constant . UnRLockType , "" ) )
2025-03-06 16:35:36 +08:00
}
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) WLock ( ctx context . Context , timeout ... time . Duration ) error {
result := rl . tryWLock ( ctx ) . ( * constant . RedisResult )
2025-03-06 16:35:36 +08:00
if result . Code == constant . UnknownInternalError {
rl . logger . Error ( result . OutputResultMessage ( ) )
return fmt . Errorf ( "get write lock failed:%w" , result )
2025-02-28 16:00:16 +08:00
}
2025-04-02 16:47:51 +08:00
if result . Code == constant . LockSuccess {
if rl . needRefresh {
2025-04-07 16:49:06 +08:00
rl . refreshOnce . Do ( func ( ) {
if rl . refreshExitChan == nil {
rl . refreshExitChan = make ( chan struct { } )
}
2025-04-02 16:47:51 +08:00
// async refresh lock timeout unitl receive exit singal
go rl . refreshLockTimeout ( ctx )
} )
}
rl . logger . Info ( "success get the write lock by key and token" , zap . String ( "key" , rl . key ) , zap . String ( "token" , rl . token ) )
2025-03-06 16:35:36 +08:00
return nil
2025-02-28 16:00:16 +08:00
}
if len ( timeout ) > 0 && timeout [ 0 ] > 0 {
2025-04-07 16:49:06 +08:00
if rl . subExitChan == nil {
rl . subExitChan = make ( chan struct { } )
}
subMsgChan := make ( chan struct { } , 1 )
sub := rl . client . Subscribe ( ctx , rl . writeWaitChanKey )
go rl . subscribeLock ( sub , subMsgChan )
2025-03-06 16:35:36 +08:00
acquireTimer := time . NewTimer ( timeout [ 0 ] )
for {
2025-02-28 16:00:16 +08:00
select {
2025-04-07 16:49:06 +08:00
case _ , ok := <- subMsgChan :
2025-02-28 16:00:16 +08:00
if ! ok {
2025-03-06 16:35:36 +08:00
err := errors . New ( "failed to read the write lock waiting for for the channel message" )
rl . logger . Error ( "failed to read the read lock waiting for for the channel message" )
return err
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
result := rl . tryWLock ( ctx ) . ( * constant . RedisResult )
2025-03-06 16:35:36 +08:00
if ( result . Code == constant . UnknownInternalError ) || ( result . Code == constant . WLockFailureWithRLockOccupancy ) || ( result . Code == constant . WLockFailureWithWLockOccupancy ) || ( result . Code == constant . WLockFailureWithNotFirstPriority ) {
rl . logger . Info ( result . OutputResultMessage ( ) )
continue
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if result . Code == constant . LockSuccess {
rl . logger . Info ( result . OutputResultMessage ( ) )
2025-04-07 16:49:06 +08:00
rl . closeSub ( sub , rl . subExitChan )
2025-03-06 16:35:36 +08:00
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
case <- acquireTimer . C :
rl . logger . Info ( "the waiting time for obtaining the write lock operation has timed out" )
2025-04-07 16:49:06 +08:00
rl . closeSub ( sub , rl . subExitChan )
// after acquire lock timeout,notice the sub channel to close
2025-04-02 16:47:51 +08:00
return constant . AcquireTimeoutErr
2025-02-28 16:00:16 +08:00
}
}
}
2025-03-06 16:35:36 +08:00
return fmt . Errorf ( "lock write lock failed:%w" , result )
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) tryWLock ( ctx context . Context ) error {
2025-03-06 16:35:36 +08:00
lockType := constant . LockType
2025-02-28 16:00:16 +08:00
2025-03-24 16:37:43 +08:00
res := rl . client . Eval ( ctx , luascript . WLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix } , rl . lockLeaseTime , rl . token )
2025-03-06 16:35:36 +08:00
val , err := res . Int ( )
if err != redis . Nil && err != nil {
return constant . NewRedisResult ( constant . UnknownInternalError , lockType , err . Error ( ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
return constant . NewRedisResult ( constant . RedisCode ( val ) , lockType , "" )
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func ( rl * RedissionRWLocker ) UnWLock ( ctx context . Context ) error {
2025-04-03 17:22:40 +08:00
res := rl . client . Eval ( ctx , luascript . UnWLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix , rl . writeWaitChanKey , rl . readWaitChanKey } , unlockMessage , rl . token )
2025-03-06 16:35:36 +08:00
val , err := res . Int ( )
2025-02-28 16:00:16 +08:00
if err != redis . Nil && err != nil {
2025-03-21 16:21:33 +08:00
rl . logger . Error ( "unlock write lock failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) , zap . Error ( err ) )
2025-03-13 16:51:50 +08:00
return fmt . Errorf ( "unlock write lock failed:%w" , constant . NewRedisResult ( constant . UnknownInternalError , constant . UnWLockType , err . Error ( ) ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
2025-03-13 16:51:50 +08:00
if ( constant . RedisCode ( val ) == constant . UnLockSuccess ) || constant . RedisCode ( val ) == constant . UnWLockSuccess {
if rl . needRefresh && ( constant . RedisCode ( val ) == constant . UnLockSuccess ) {
2025-03-06 16:35:36 +08:00
rl . cancelRefreshLockTime ( )
}
rl . logger . Info ( "unlock write lock success" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if ( constant . RedisCode ( val ) == constant . UnWLockFailureWithRLockOccupancy ) || ( constant . RedisCode ( val ) == constant . UnWLockFailureWithWLockOccupancy ) {
rl . logger . Info ( "unlock write lock failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) )
2025-03-13 16:51:50 +08:00
return fmt . Errorf ( "unlock write lock failed:%w" , constant . NewRedisResult ( constant . RedisCode ( val ) , constant . UnWLockType , "" ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-25 17:00:09 +08:00
// TODO 优化 panic
func GetRWLocker ( client * redis . Client , conf * RedissionLockConfig ) * RedissionRWLocker {
if conf . Token == "" {
token , err := uuid . NewV4 ( )
if err != nil {
panic ( err )
}
conf . Token = token . String ( )
2025-02-28 16:00:16 +08:00
}
2025-03-25 17:00:09 +08:00
if conf . Prefix == "" {
conf . Prefix = "redission-rwlock"
2025-02-28 16:00:16 +08:00
}
2025-03-05 16:42:59 +08:00
2025-03-25 17:00:09 +08:00
if conf . TimeoutPrefix == "" {
conf . TimeoutPrefix = "rwlock_timeout"
2025-03-07 16:16:26 +08:00
}
2025-03-25 17:00:09 +08:00
if conf . ChanPrefix == "" {
conf . ChanPrefix = "redission-rwlock-channel"
2025-02-28 16:00:16 +08:00
}
2025-03-05 16:42:59 +08:00
2025-03-25 17:00:09 +08:00
if conf . LockLeaseTime == 0 {
conf . LockLeaseTime = internalLockLeaseTime
2025-02-28 16:00:16 +08:00
}
2025-03-07 16:16:26 +08:00
2025-03-12 16:24:28 +08:00
r := & redissionLocker {
2025-03-25 17:00:09 +08:00
token : conf . Token ,
key : strings . Join ( [ ] string { conf . Prefix , conf . Key } , ":" ) ,
needRefresh : conf . NeedRefresh ,
lockLeaseTime : conf . LockLeaseTime ,
2025-03-12 16:24:28 +08:00
client : client ,
2025-04-07 16:49:06 +08:00
refreshOnce : & sync . Once { } ,
2025-03-12 16:24:28 +08:00
logger : logger . GetLoggerInstance ( ) ,
}
2025-02-28 16:00:16 +08:00
2025-03-06 16:35:36 +08:00
rwLocker := & RedissionRWLocker {
2025-03-07 16:16:26 +08:00
redissionLocker : * r ,
2025-04-03 17:22:40 +08:00
writeWaitChanKey : strings . Join ( [ ] string { conf . ChanPrefix , conf . Key , "write" } , ":" ) ,
readWaitChanKey : strings . Join ( [ ] string { conf . ChanPrefix , conf . Key , "read" } , ":" ) ,
2025-03-25 17:00:09 +08:00
rwTokenTimeoutPrefix : conf . TimeoutPrefix ,
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
return rwLocker
2025-02-28 16:00:16 +08:00
}
2025-03-24 16:37:43 +08:00
func InitRWLocker ( key string , token string , lockLeaseTime uint64 , needRefresh bool ) * RedissionRWLocker {
2025-03-25 17:00:09 +08:00
conf := & RedissionLockConfig {
2025-03-24 16:37:43 +08:00
Key : key ,
Token : token ,
LockLeaseTime : lockLeaseTime ,
NeedRefresh : needRefresh ,
}
2025-03-25 17:00:09 +08:00
return GetRWLocker ( GetRedisClientInstance ( ) , conf )
2025-03-24 16:37:43 +08:00
}