2025-02-28 16:00:16 +08:00
package distributed_lock
import (
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-02-28 16:00:16 +08:00
"github.com/go-redis/redis"
uuid "github.com/google/uuid"
"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-03-07 16:16:26 +08:00
rwTokenTimeoutPrefix string
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) RLock ( timeout ... time . Duration ) error {
2025-02-28 16:00:16 +08:00
if rl . exit == nil {
rl . exit = make ( chan struct { } )
}
2025-03-05 16:42:59 +08:00
2025-03-06 16:35:36 +08:00
result := rl . tryRLock ( ) . ( * constant . RedisResult )
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 {
rl . once . Do ( func ( ) {
// async refresh lock timeout unitl receive exit singal
go rl . refreshLockTimeout ( )
} )
}
rl . logger . Info ( "success get the read 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
}
2025-03-05 16:42:59 +08:00
subMsg := make ( chan struct { } , 1 )
defer close ( subMsg )
2025-03-07 16:16:26 +08:00
sub := rl . client . Subscribe ( rl . waitChanKey )
2025-02-28 16:00:16 +08:00
defer sub . Close ( )
2025-03-05 16:42:59 +08:00
go rl . subscribeLock ( sub , subMsg )
2025-02-28 16:00:16 +08:00
if len ( timeout ) > 0 && timeout [ 0 ] > 0 {
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-03-05 16:42:59 +08:00
case _ , ok := <- subMsg :
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-03-06 16:35:36 +08:00
resultErr := rl . tryRLock ( ) . ( * constant . RedisResult )
if ( resultErr . Code == constant . RLockFailureWithWLockOccupancy ) || ( resultErr . Code == constant . UnknownInternalError ) {
2025-03-05 16:42:59 +08:00
rl . logger . Info ( resultErr . OutputResultMessage ( ) )
continue
2025-02-28 16:00:16 +08:00
}
2025-03-05 16:42:59 +08:00
if resultErr . Code == constant . LockSuccess {
rl . logger . Info ( resultErr . OutputResultMessage ( ) )
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-05 16:42:59 +08:00
case <- acquireTimer . C :
err := errors . New ( "the waiting time for obtaining the read lock operation has timed out" )
rl . logger . Info ( "the waiting time for obtaining the read lock operation has timed out" )
return err
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-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) tryRLock ( ) error {
2025-03-05 16:42:59 +08:00
lockType := constant . LockType
2025-03-06 16:35:36 +08:00
2025-03-07 16:16:26 +08:00
res := rl . client . Eval ( 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-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) refreshLockTimeout ( ) {
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-07 16:16:26 +08:00
res := rl . client . Eval ( 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 )
case <- rl . exit :
2025-03-12 16:24:28 +08:00
return
2025-02-28 16:00:16 +08:00
}
}
}
2025-03-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) UnRLock ( ) 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-03-07 16:16:26 +08:00
res := rl . client . Eval ( luascript . UnRLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix , rl . waitChanKey } , 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-07 16:16:26 +08:00
return fmt . Errorf ( "unlock read lock failed:%w" , constant . NewRedisResult ( constant . UnknownInternalError , constant . UnLockType , 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 ) {
if rl . needRefresh {
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 ) )
return fmt . Errorf ( "unlock read lock failed:%w" , constant . NewRedisResult ( constant . UnRLockFailureWithWLockOccupancy , constant . UnLockType , "" ) )
}
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) WLock ( timeout ... time . Duration ) error {
2025-02-28 16:00:16 +08:00
if rl . exit == nil {
rl . exit = make ( chan struct { } )
}
2025-03-06 16:35:36 +08:00
result := rl . tryWLock ( ) . ( * constant . RedisResult )
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-03-06 16:35:36 +08:00
if ( result . Code == constant . LockSuccess ) && rl . needRefresh {
2025-02-28 16:00:16 +08:00
rl . once . Do ( func ( ) {
2025-03-05 16:42:59 +08:00
// async refresh lock timeout unitl receive exit singal
2025-02-28 16:00:16 +08:00
go rl . refreshLockTimeout ( )
} )
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
subMsg := make ( chan struct { } , 1 )
defer close ( subMsg )
2025-03-07 16:16:26 +08:00
sub := rl . client . Subscribe ( rl . waitChanKey )
2025-02-28 16:00:16 +08:00
defer sub . Close ( )
2025-03-06 16:35:36 +08:00
go rl . subscribeLock ( sub , subMsg )
2025-02-28 16:00:16 +08:00
if len ( timeout ) > 0 && timeout [ 0 ] > 0 {
2025-03-06 16:35:36 +08:00
acquireTimer := time . NewTimer ( timeout [ 0 ] )
for {
2025-02-28 16:00:16 +08:00
select {
2025-03-06 16:35:36 +08:00
case _ , ok := <- subMsg :
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-06 16:35:36 +08:00
result := rl . tryWLock ( ) . ( * constant . RedisResult )
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 ( ) )
return nil
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
case <- acquireTimer . C :
err := errors . New ( "the waiting time for obtaining the write lock operation has timed out" )
rl . logger . Info ( "the waiting time for obtaining the write lock operation has timed out" )
return err
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-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) tryWLock ( ) error {
lockType := constant . LockType
2025-02-28 16:00:16 +08:00
2025-03-07 16:16:26 +08:00
res := rl . client . Eval ( 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-06 16:35:36 +08:00
func ( rl * RedissionRWLocker ) UnWLock ( ) error {
2025-03-07 16:16:26 +08:00
res := rl . client . Eval ( luascript . UnWLockScript , [ ] string { rl . key , rl . rwTokenTimeoutPrefix , rl . waitChanKey } , 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 write lock failed" , zap . String ( "token" , rl . token ) , zap . String ( "key" , rl . key ) , zap . Error ( err ) )
return fmt . Errorf ( "unlock write lock failed:%w" , constant . NewRedisResult ( constant . UnknownInternalError , constant . UnLockType , err . Error ( ) ) )
2025-02-28 16:00:16 +08:00
}
2025-03-06 16:35:36 +08:00
if constant . RedisCode ( val ) == constant . UnLockSuccess {
if rl . needRefresh {
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 ) )
return fmt . Errorf ( "unlock write lock failed:%w" , constant . NewRedisResult ( constant . RedisCode ( val ) , constant . UnLockType , "" ) )
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-06 16:35:36 +08:00
func GetRWLocker ( client * redis . Client , ops * RedissionLockConfig ) * RedissionRWLocker {
2025-03-12 16:24:28 +08:00
if ops . Token == "" {
ops . Token = uuid . New ( ) . String ( )
2025-02-28 16:00:16 +08:00
}
2025-03-12 16:24:28 +08:00
if ops . Prefix == "" {
2025-02-28 16:00:16 +08:00
ops . Prefix = "redission-rwlock"
}
2025-03-05 16:42:59 +08:00
2025-03-12 16:24:28 +08:00
if ops . TimeoutPrefix == "" {
2025-03-07 16:16:26 +08:00
ops . TimeoutPrefix = "rwlock_timeout"
}
2025-03-12 16:24:28 +08:00
if ops . ChanPrefix == "" {
2025-02-28 16:00:16 +08:00
ops . ChanPrefix = "redission-rwlock-channel"
}
2025-03-05 16:42:59 +08:00
2025-02-28 16:00:16 +08:00
if ops . LockLeaseTime == 0 {
2025-03-11 15:35:15 +08:00
ops . 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 {
token : ops . Token ,
key : strings . Join ( [ ] string { ops . Prefix , ops . Key } , ":" ) ,
needRefresh : ops . NeedRefresh ,
lockLeaseTime : ops . LockLeaseTime ,
waitChanKey : strings . Join ( [ ] string { ops . ChanPrefix , ops . Key , "write" } , ":" ) ,
client : client ,
exit : make ( chan struct { } ) ,
once : & sync . Once { } ,
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 ,
rwTokenTimeoutPrefix : ops . 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
}