├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── entry.go ├── examples ├── mutex │ └── mutex.go └── rlock │ └── rlock.go ├── fairlock.go ├── gedission.go ├── go.mod ├── go.sum ├── goid.go ├── goid_test.go ├── helper.go ├── locker.go ├── mutex.go ├── mutex_test.go ├── rlock.go ├── rlock_test.go └── rwlock.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | ## go-redisson 2 | 3 | 一个借鉴 redisson 用 go 实现的 Redis 分布式锁的库。 4 | 5 | ## 安装 6 | 7 | ```shell 8 | go get github.com/cheerego/go-redisson 9 | ``` 10 | 11 | 12 | ## 支持锁类型 13 | 14 | * Mutex [示例](#Mutex) 特性: 15 | * 互斥锁 (X Lock)。 16 | * 和 go 标准库 sync.Mutex 用起来差不多。 17 | * 不支持可重入。 18 | * 支持 WatchDog。 19 | 20 | * RLock[示例](#RLock) 特性: 21 | * 互斥可重入锁。 22 | * 和 redisson 使用起来差不多。 23 | * 支持同一个协程重复加锁。 24 | * 支持 WatchDog。 25 | 26 | ## 特性 27 | 28 | * tryLock,if waitTime > 0, wait `waitTime` milliseconds to try to obtain lock by while true and redis pub sub. 29 | * watchdog, if leaseTime = -1, start a time.Ticker(defaultWatchDogTime / 3) to renew lock expiration time. 30 | 31 | ## 配置 32 | 33 | ### WatchDogTimeout 34 | 35 | ```go 36 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 37 | ``` 38 | 39 | 40 | ## 示例 41 | 42 | 43 | ### Mutex 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "github.com/cheerego/go-redisson" 50 | "github.com/go-redis/redis/v8" 51 | "github.com/pkg/errors" 52 | "log" 53 | "time" 54 | ) 55 | 56 | func main() { 57 | 58 | // create redis client 59 | rdb := redis.NewClient(&redis.Options{ 60 | Addr: "localhost:6379", 61 | Password: "", // no password set 62 | DB: 0, // use default DB 63 | }) 64 | defer rdb.Close() 65 | 66 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 67 | 68 | test1(g) 69 | test2(g) 70 | } 71 | 72 | // can't obtain lock in a same goroutine 73 | func test1(g *godisson.Godisson) { 74 | m1 := g.NewMutex("godisson") 75 | m2 := g.NewMutex("godisson") 76 | 77 | err := m1.TryLock(-1, 20000) 78 | if errors.Is(err, godisson.ErrLockNotObtained) { 79 | log.Println("can't obtained lock") 80 | } else if err != nil { 81 | log.Fatalln(err) 82 | } 83 | defer m1.Unlock() 84 | 85 | // because waitTime = -1, waitTime < 0, try once, will return ErrLockNotObtained 86 | err = m2.TryLock(-1, 20000) 87 | if errors.Is(err, godisson.ErrLockNotObtained) { 88 | log.Println("m2 must not obtained lock") 89 | } else if err != nil { 90 | log.Fatalln(err) 91 | } 92 | time.Sleep(10 * time.Second) 93 | } 94 | 95 | func test2(g *godisson.Godisson) { 96 | m1 := g.NewMutex("godisson") 97 | m2 := g.NewMutex("godisson") 98 | 99 | go func() { 100 | err := m1.TryLock(-1, 20000) 101 | if errors.Is(err, godisson.ErrLockNotObtained) { 102 | log.Println("can't obtained lock") 103 | } else if err != nil { 104 | log.Fatalln(err) 105 | } 106 | time.Sleep(10 * time.Second) 107 | m1.Unlock() 108 | }() 109 | 110 | // waitTime > 0, after 10 milliseconds will obtain the lock 111 | go func() { 112 | time.Sleep(1 * time.Second) 113 | 114 | err := m2.TryLock(15000, 20000) 115 | if errors.Is(err, godisson.ErrLockNotObtained) { 116 | log.Println("m2 must not obtained lock") 117 | } else if err != nil { 118 | log.Fatalln(err) 119 | } 120 | time.Sleep(10 * time.Second) 121 | 122 | m2.Unlock() 123 | }() 124 | time.Sleep(20 * time.Second) 125 | 126 | } 127 | 128 | 129 | ``` 130 | 131 | 132 | ### RLock 133 | ```go 134 | package main 135 | 136 | import ( 137 | "github.com/cheerego/go-redisson" 138 | "github.com/go-redis/redis/v8" 139 | "log" 140 | "time" 141 | ) 142 | 143 | func main() { 144 | 145 | // create redis client 146 | rdb := redis.NewClient(&redis.Options{ 147 | Addr: "localhost:6379", 148 | Password: "", // no password set 149 | DB: 0, // use default DB 150 | }) 151 | defer rdb.Close() 152 | 153 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 154 | 155 | // lock with watchdog without retry 156 | lock := g.NewRLock("godisson") 157 | 158 | err := lock.Lock() 159 | if err == godisson.ErrLockNotObtained { 160 | log.Println("Could not obtain lock") 161 | } else if err != nil { 162 | log.Fatalln(err) 163 | } 164 | defer lock.Unlock() 165 | 166 | // lock with retry、watchdog 167 | // leaseTime value is -1, enable watchdog 168 | lock2 := g.NewRLock("godission-try-watchdog") 169 | 170 | err = lock2.TryLock(20000, -1) 171 | if err == godisson.ErrLockNotObtained { 172 | log.Println("Could not obtain lock") 173 | } else if err != nil { 174 | log.Fatalln(err) 175 | } 176 | time.Sleep(10 * time.Second) 177 | defer lock.Unlock() 178 | } 179 | 180 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## go-redisson 2 | 3 | a Redisson like distributed locking implementation using Redis. 4 | 5 | **Explanation** 6 | 7 | [中文](README-zh.md) 8 | 9 | ## Installation 10 | 11 | ```shell 12 | go get github.com/cheerego/go-redisson 13 | ``` 14 | 15 | 16 | ## Support Lock Category 17 | 18 | * Mutex [Example](#Mutex) 19 | * Exclusive Lock (X Lock). 20 | * use it like std package sync.Mutex. 21 | * not a reentrant lock that can't lock twice in a same goroutine. 22 | 23 | * RLock [Example](#Rlock) 24 | * Exclusive Reentrant Lock. 25 | * use it like java redisson. 26 | * a reentrant lock that can lock many times in a same goroutine. 27 | 28 | ## Features 29 | 30 | * tryLock,if waitTime > 0, wait `waitTime` milliseconds to try to obtain lock by while true and redis pub sub. 31 | * watchdog, if leaseTime = -1, start a time.Ticker(defaultWatchDogTime / 3) to renew lock expiration time. 32 | 33 | ## Options 34 | 35 | ### WatchDogTimeout 36 | 37 | ```go 38 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 39 | ``` 40 | 41 | 42 | ## Examples 43 | 44 | 45 | ### Mutex 46 | 47 | ```go 48 | package main 49 | 50 | import ( 51 | "github.com/cheerego/go-redisson" 52 | "github.com/go-redis/redis/v8" 53 | "github.com/pkg/errors" 54 | "log" 55 | "time" 56 | ) 57 | 58 | func main() { 59 | 60 | // create redis client 61 | rdb := redis.NewClient(&redis.Options{ 62 | Addr: "localhost:6379", 63 | Password: "", // no password set 64 | DB: 0, // use default DB 65 | }) 66 | defer rdb.Close() 67 | 68 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 69 | 70 | test1(g) 71 | test2(g) 72 | } 73 | 74 | // can't obtain lock in a same goroutine 75 | func test1(g *godisson.Godisson) { 76 | m1 := g.NewMutex("godisson") 77 | m2 := g.NewMutex("godisson") 78 | 79 | err := m1.TryLock(-1, 20000) 80 | if errors.Is(err, godisson.ErrLockNotObtained) { 81 | log.Println("can't obtained lock") 82 | } else if err != nil { 83 | log.Fatalln(err) 84 | } 85 | defer m1.Unlock() 86 | 87 | // because waitTime = -1, waitTime < 0, try once, will return ErrLockNotObtained 88 | err = m2.TryLock(-1, 20000) 89 | if errors.Is(err, godisson.ErrLockNotObtained) { 90 | log.Println("m2 must not obtained lock") 91 | } else if err != nil { 92 | log.Fatalln(err) 93 | } 94 | time.Sleep(10 * time.Second) 95 | } 96 | 97 | func test2(g *godisson.Godisson) { 98 | m1 := g.NewMutex("godisson") 99 | m2 := g.NewMutex("godisson") 100 | 101 | go func() { 102 | err := m1.TryLock(-1, 20000) 103 | if errors.Is(err, godisson.ErrLockNotObtained) { 104 | log.Println("can't obtained lock") 105 | } else if err != nil { 106 | log.Fatalln(err) 107 | } 108 | time.Sleep(10 * time.Second) 109 | m1.Unlock() 110 | }() 111 | 112 | // waitTime > 0, after 10 milliseconds will obtain the lock 113 | go func() { 114 | time.Sleep(1 * time.Second) 115 | 116 | err := m2.TryLock(15000, 20000) 117 | if errors.Is(err, godisson.ErrLockNotObtained) { 118 | log.Println("m2 must not obtained lock") 119 | } else if err != nil { 120 | log.Fatalln(err) 121 | } 122 | time.Sleep(10 * time.Second) 123 | 124 | m2.Unlock() 125 | }() 126 | time.Sleep(20 * time.Second) 127 | 128 | } 129 | 130 | 131 | ``` 132 | 133 | 134 | ### RLock 135 | ```go 136 | package main 137 | 138 | import ( 139 | "github.com/cheerego/go-redisson" 140 | "github.com/go-redis/redis/v8" 141 | "log" 142 | "time" 143 | ) 144 | 145 | func main() { 146 | 147 | // create redis client 148 | rdb := redis.NewClient(&redis.Options{ 149 | Addr: "localhost:6379", 150 | Password: "", // no password set 151 | DB: 0, // use default DB 152 | }) 153 | defer rdb.Close() 154 | 155 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 156 | 157 | // lock with watchdog without retry 158 | lock := g.NewRLock("godisson") 159 | 160 | err := lock.Lock() 161 | if err == godisson.ErrLockNotObtained { 162 | log.Println("Could not obtain lock") 163 | } else if err != nil { 164 | log.Fatalln(err) 165 | } 166 | defer lock.Unlock() 167 | 168 | // lock with retry、watchdog 169 | // leaseTime value is -1, enable watchdog 170 | lock2 := g.NewRLock("godission-try-watchdog") 171 | 172 | err = lock2.TryLock(20000, -1) 173 | if err == godisson.ErrLockNotObtained { 174 | log.Println("Could not obtain lock") 175 | } else if err != nil { 176 | log.Fatalln(err) 177 | } 178 | time.Sleep(10 * time.Second) 179 | defer lock.Unlock() 180 | } 181 | 182 | ``` -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | type RenewEntry struct { 9 | sync.Mutex 10 | goroutineIds map[uint64]int64 11 | cancelFunc context.CancelFunc 12 | } 13 | 14 | func NewRenewEntry() *RenewEntry { 15 | return &RenewEntry{ 16 | goroutineIds: make(map[uint64]int64), 17 | } 18 | } 19 | 20 | func (r *RenewEntry) addGoroutineId(goroutineId uint64) { 21 | r.Lock() 22 | defer r.Unlock() 23 | count, ok := r.goroutineIds[goroutineId] 24 | if ok { 25 | count++ 26 | } else { 27 | count = 1 28 | } 29 | r.goroutineIds[goroutineId] = count 30 | } 31 | 32 | func (r *RenewEntry) removeGoroutineId(goroutineId uint64) { 33 | r.Lock() 34 | defer r.Unlock() 35 | 36 | count, ok := r.goroutineIds[goroutineId] 37 | if !ok { 38 | return 39 | } 40 | count-- 41 | if count == 0 { 42 | delete(r.goroutineIds, goroutineId) 43 | } else { 44 | r.goroutineIds[goroutineId] = count 45 | } 46 | } 47 | 48 | func (r *RenewEntry) hasNoThreads() bool { 49 | return len(r.goroutineIds) == 0 50 | } 51 | -------------------------------------------------------------------------------- /examples/mutex/mutex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/cheerego/go-redisson" 5 | "github.com/go-redis/redis/v8" 6 | "github.com/pkg/errors" 7 | "log" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | 13 | // create redis client 14 | rdb := redis.NewClient(&redis.Options{ 15 | Addr: "localhost:6379", 16 | Password: "", // no password set 17 | DB: 0, // use default DB 18 | }) 19 | defer rdb.Close() 20 | 21 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 22 | 23 | test1(g) 24 | test2(g) 25 | } 26 | 27 | // can't obtain lock in a same goroutine 28 | func test1(g *godisson.Godisson) { 29 | m1 := g.NewMutex("godisson") 30 | m2 := g.NewMutex("godisson") 31 | 32 | err := m1.TryLock(-1, 20000) 33 | if errors.Is(err, godisson.ErrLockNotObtained) { 34 | log.Println("can't obtained lock") 35 | } else if err != nil { 36 | log.Fatalln(err) 37 | } 38 | defer m1.Unlock() 39 | 40 | // because waitTime = -1, waitTime < 0, try once, will return ErrLockNotObtained 41 | err = m2.TryLock(-1, 20000) 42 | if errors.Is(err, godisson.ErrLockNotObtained) { 43 | log.Println("m2 must not obtained lock") 44 | } else if err != nil { 45 | log.Fatalln(err) 46 | } 47 | time.Sleep(10 * time.Second) 48 | } 49 | 50 | func test2(g *godisson.Godisson) { 51 | m1 := g.NewMutex("godisson") 52 | m2 := g.NewMutex("godisson") 53 | 54 | go func() { 55 | err := m1.TryLock(-1, 20000) 56 | if errors.Is(err, godisson.ErrLockNotObtained) { 57 | log.Println("can't obtained lock") 58 | } else if err != nil { 59 | log.Fatalln(err) 60 | } 61 | time.Sleep(10 * time.Second) 62 | m1.Unlock() 63 | }() 64 | 65 | // waitTime > 0, after 10 milliseconds will obtain the lock 66 | go func() { 67 | time.Sleep(1 * time.Second) 68 | 69 | err := m2.TryLock(15000, 20000) 70 | if errors.Is(err, godisson.ErrLockNotObtained) { 71 | log.Println("m2 must not obtained lock") 72 | } else if err != nil { 73 | log.Fatalln(err) 74 | } 75 | time.Sleep(10 * time.Second) 76 | 77 | m2.Unlock() 78 | }() 79 | time.Sleep(20 * time.Second) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /examples/rlock/rlock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/cheerego/go-redisson" 5 | "github.com/go-redis/redis/v8" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | 12 | // create redis client 13 | rdb := redis.NewClient(&redis.Options{ 14 | Addr: "localhost:6379", 15 | Password: "", // no password set 16 | DB: 0, // use default DB 17 | }) 18 | defer rdb.Close() 19 | 20 | g := godisson.NewGodisson(rdb, godisson.WithWatchDogTimeout(30*time.Second)) 21 | 22 | // lock with watchdog without retry 23 | lock := g.NewRLock("godisson") 24 | 25 | err := lock.Lock() 26 | if err == godisson.ErrLockNotObtained { 27 | log.Println("Could not obtain lock") 28 | } else if err != nil { 29 | log.Fatalln(err) 30 | } 31 | defer lock.Unlock() 32 | 33 | // lock with retry、watchdog 34 | // leaseTime value is -1, enable watchdog 35 | lock2 := g.NewRLock("godission-try-watchdog") 36 | 37 | err = lock2.TryLock(20000, -1) 38 | if err == godisson.ErrLockNotObtained { 39 | log.Println("Could not obtain lock") 40 | } else if err != nil { 41 | log.Fatalln(err) 42 | } 43 | time.Sleep(10 * time.Second) 44 | defer lock.Unlock() 45 | } 46 | -------------------------------------------------------------------------------- /fairlock.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | -------------------------------------------------------------------------------- /gedission.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis/v8" 6 | cmap "github.com/orcaman/concurrent-map" 7 | uuid "github.com/satori/go.uuid" 8 | "log" 9 | "time" 10 | ) 11 | 12 | type Godisson struct { 13 | c *redis.Client 14 | watchDogTimeout time.Duration 15 | uuid string 16 | // key uuid:key, value entry 17 | RenewMap cmap.ConcurrentMap 18 | } 19 | 20 | var DefaultWatchDogTimeout = 30 * time.Second 21 | 22 | func NewGodisson(redisClient *redis.Client, opts ...OptionFunc) *Godisson { 23 | g := &Godisson{ 24 | c: redisClient, 25 | uuid: uuid.NewV4().String(), 26 | watchDogTimeout: DefaultWatchDogTimeout, 27 | RenewMap: cmap.New(), 28 | } 29 | for _, opt := range opts { 30 | opt(g) 31 | } 32 | return g 33 | } 34 | 35 | type OptionFunc func(g *Godisson) 36 | 37 | func WithWatchDogTimeout(t time.Duration) OptionFunc { 38 | return func(g *Godisson) { 39 | if t.Seconds() < 30 { 40 | t = DefaultWatchDogTimeout 41 | log.Println("watchDogTimeout is too small, so config default ") 42 | } 43 | g.watchDogTimeout = t 44 | } 45 | } 46 | 47 | func (g *Godisson) NewRLock(key string) *RLock { 48 | return newRLock(key, g) 49 | } 50 | 51 | func (g *Godisson) NewMutex(key string) *Mutex { 52 | return newMutex(key, g) 53 | } 54 | 55 | func (g *Godisson) getEntryName(key string) string { 56 | return fmt.Sprintf("%s:%s", g.uuid, key) 57 | } 58 | 59 | func (g *Godisson) getChannelName(key string) string { 60 | return fmt.Sprintf("{gedisson_lock__channel}:%s)", key) 61 | } 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cheerego/go-redisson 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/go-redis/redis/v8 v8.11.5 7 | github.com/orcaman/concurrent-map v1.0.0 // indirect 8 | github.com/pkg/errors v0.9.1 9 | github.com/satori/go.uuid v1.2.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 9 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 10 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 11 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 12 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 13 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 14 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 15 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 16 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 18 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 19 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 20 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 21 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 22 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 25 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 26 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 30 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 31 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 32 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 33 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 34 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 35 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 36 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 37 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 38 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 39 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 40 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 41 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 42 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 43 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 44 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 45 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 46 | github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= 47 | github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= 48 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 49 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 52 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 53 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 54 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 55 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 56 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 57 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 58 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 59 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 60 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 61 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 62 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 63 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 64 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 65 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= 66 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 67 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 69 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 70 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 71 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 72 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 73 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 83 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 85 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 86 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 87 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 88 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 89 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 90 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 91 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 92 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 93 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 94 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 95 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 96 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 97 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 98 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 99 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 100 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 101 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 102 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 103 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 104 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 107 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 108 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 109 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 110 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 111 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 112 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 113 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 114 | -------------------------------------------------------------------------------- /goid.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/pkg/errors" 7 | "runtime" 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | var goroutineSpace = []byte("goroutine ") 13 | 14 | func gid() (uint64, error) { 15 | bp := littleBuf.Get().(*[]byte) 16 | defer littleBuf.Put(bp) 17 | b := *bp 18 | b = b[:runtime.Stack(b, false)] 19 | // Parse the 4707 out of "goroutine 4707 [" 20 | b = bytes.TrimPrefix(b, goroutineSpace) 21 | i := bytes.IndexByte(b, ' ') 22 | if i < 0 { 23 | return 0, errors.New(fmt.Sprintf("No space found in %q", b)) 24 | } 25 | b = b[:i] 26 | n, err := parseUintBytes(b, 10, 64) 27 | if err != nil { 28 | return 0, errors.New(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) 29 | } 30 | return n, nil 31 | } 32 | 33 | var littleBuf = sync.Pool{ 34 | New: func() interface{} { 35 | buf := make([]byte, 64) 36 | return &buf 37 | }, 38 | } 39 | 40 | // parseUintBytes is like strconv.ParseUint, but using a []byte. 41 | func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) { 42 | var cutoff, maxVal uint64 43 | 44 | if bitSize == 0 { 45 | bitSize = int(strconv.IntSize) 46 | } 47 | 48 | s0 := s 49 | switch { 50 | case len(s) < 1: 51 | err = strconv.ErrSyntax 52 | goto Error 53 | 54 | case 2 <= base && base <= 36: 55 | // valid base; nothing to do 56 | 57 | case base == 0: 58 | // Look for octal, hex prefix. 59 | switch { 60 | case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): 61 | base = 16 62 | s = s[2:] 63 | if len(s) < 1 { 64 | err = strconv.ErrSyntax 65 | goto Error 66 | } 67 | case s[0] == '0': 68 | base = 8 69 | default: 70 | base = 10 71 | } 72 | 73 | default: 74 | err = errors.New("invalid base " + strconv.Itoa(base)) 75 | goto Error 76 | } 77 | 78 | n = 0 79 | cutoff = cutoff64(base) 80 | maxVal = 1<= base { 98 | n = 0 99 | err = strconv.ErrSyntax 100 | goto Error 101 | } 102 | 103 | if n >= cutoff { 104 | // n*base overflows 105 | n = 1<<64 - 1 106 | err = strconv.ErrRange 107 | goto Error 108 | } 109 | n *= uint64(base) 110 | 111 | n1 := n + uint64(v) 112 | if n1 < n || n1 > maxVal { 113 | // n+v overflows 114 | n = 1<<64 - 1 115 | err = strconv.ErrRange 116 | goto Error 117 | } 118 | n = n1 119 | } 120 | 121 | return n, nil 122 | 123 | Error: 124 | return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} 125 | } 126 | 127 | // Return the first number n such that n*base >= 1<<64. 128 | func cutoff64(base int) uint64 { 129 | if base < 2 { 130 | return 0 131 | } 132 | return (1<<64-1)/uint64(base) + 1 133 | } 134 | -------------------------------------------------------------------------------- /goid_test.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_gid(t *testing.T) { 8 | goid, err := gid() 9 | if err != nil { 10 | t.Errorf("gid() error = %v", err) 11 | return 12 | } 13 | t.Log(goid) 14 | } 15 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func currentTimeMillis() int64 { 8 | return time.Now().UnixNano() / 1e6 9 | } 10 | -------------------------------------------------------------------------------- /locker.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | type Locker interface { 4 | TryLock(waitTime int64, leaseTime int64) error 5 | Unlock() (int64, error) 6 | } 7 | -------------------------------------------------------------------------------- /mutex.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "context" 5 | "github.com/pkg/errors" 6 | "net" 7 | "time" 8 | ) 9 | 10 | type Mutex struct { 11 | Key string 12 | g *Godisson 13 | cancelFunc context.CancelFunc 14 | } 15 | 16 | var _ Locker = (*Mutex)(nil) 17 | 18 | func newMutex(key string, g *Godisson) *Mutex { 19 | return &Mutex{Key: key, g: g} 20 | } 21 | 22 | func (m *Mutex) TryLock(waitTime int64, leaseTime int64) error { 23 | wait := waitTime 24 | current := currentTimeMillis() 25 | ttl, err := m.tryAcquire(waitTime, leaseTime) 26 | if err != nil { 27 | return err 28 | } 29 | if ttl == 0 { 30 | return nil 31 | } 32 | wait -= currentTimeMillis() - current 33 | if wait <= 0 { 34 | return ErrLockNotObtained 35 | } 36 | current = currentTimeMillis() 37 | // PubSub 38 | sub := m.g.c.Subscribe(context.TODO(), m.g.getChannelName(m.Key)) 39 | defer sub.Close() 40 | timeoutCtx, timeoutCancel := context.WithTimeout(context.TODO(), time.Duration(wait)*time.Millisecond) 41 | defer timeoutCancel() 42 | _, err = sub.ReceiveMessage(timeoutCtx) 43 | if err != nil { 44 | return ErrLockNotObtained 45 | } 46 | 47 | wait -= currentTimeMillis() - current 48 | if wait <= 0 { 49 | return ErrLockNotObtained 50 | } 51 | 52 | for { 53 | currentTime := currentTimeMillis() 54 | ttl, err = m.tryAcquire(waitTime, leaseTime) 55 | if ttl == 0 { 56 | return nil 57 | } 58 | wait -= currentTimeMillis() - currentTime 59 | if wait <= 0 { 60 | return ErrLockNotObtained 61 | } 62 | currentTime = currentTimeMillis() 63 | 64 | var target *net.OpError 65 | if ttl >= 0 && ttl < wait { 66 | tCtx, _ := context.WithTimeout(context.TODO(), time.Duration(ttl)*time.Millisecond) 67 | _, err := sub.ReceiveMessage(tCtx) 68 | if err != nil { 69 | if errors.As(err, &target) { 70 | continue 71 | } 72 | } 73 | } else { 74 | tCtx, _ := context.WithTimeout(context.TODO(), time.Duration(wait)*time.Millisecond) 75 | _, err := sub.ReceiveMessage(tCtx) 76 | 77 | if err != nil { 78 | if errors.As(err, &target) { 79 | continue 80 | } 81 | } 82 | } 83 | wait -= currentTimeMillis() - currentTime 84 | if wait <= 0 { 85 | return ErrLockNotObtained 86 | } 87 | } 88 | } 89 | 90 | func (m *Mutex) tryAcquire(waitTime int64, leaseTime int64) (int64, error) { 91 | 92 | if leaseTime != -1 { 93 | return m.tryAcquireInner(waitTime, leaseTime) 94 | } 95 | 96 | ttl, err := m.tryAcquire(waitTime, m.g.watchDogTimeout.Milliseconds()) 97 | if err != nil { 98 | return 0, err 99 | } 100 | if ttl == 0 { 101 | m.renewExpirationScheduler() 102 | } 103 | return ttl, nil 104 | } 105 | 106 | func (m *Mutex) tryAcquireInner(waitTime int64, leaseTime int64) (int64, error) { 107 | result, err := m.g.c.Eval(context.Background(), ` 108 | if (redis.call('exists', KEYS[1]) == 0) then 109 | redis.call('set', KEYS[1], 1); 110 | redis.call('pexpire', KEYS[1], ARGV[1]); 111 | return 0; 112 | end; 113 | return redis.call('pttl', KEYS[1]); 114 | `, []string{m.Key}, leaseTime).Result() 115 | if err != nil { 116 | return 0, err 117 | } 118 | 119 | if ttl, ok := result.(int64); ok { 120 | return ttl, nil 121 | } else { 122 | return 0, errors.Errorf("tryAcquireInner result converter to int64 error, value is %v", result) 123 | } 124 | } 125 | 126 | func (m *Mutex) Unlock() (int64, error) { 127 | defer m.cancelExpirationRenewal() 128 | result, err := m.g.c.Eval(context.TODO(), ` 129 | if (redis.call('exists', KEYS[1]) == 0) then 130 | return 0; 131 | end; 132 | redis.call('del', KEYS[1]); 133 | redis.call('publish', KEYS[2], ARGV[1]); 134 | return 1; 135 | `, []string{m.Key, m.g.getChannelName(m.Key)}, UNLOCK_MESSAGE).Result() 136 | if err != nil { 137 | return 0, err 138 | } 139 | 140 | if b, ok := result.(int64); ok { 141 | return b, nil 142 | } else { 143 | return 0, errors.Errorf("unlock result converter to bool error, value is %v", result) 144 | } 145 | } 146 | 147 | func (m *Mutex) renewExpirationScheduler() { 148 | cancel, cancelFunc := context.WithCancel(context.TODO()) 149 | m.cancelFunc = cancelFunc 150 | go m.renewExpirationSchedulerGoroutine(cancel) 151 | } 152 | 153 | func (m *Mutex) renewExpirationSchedulerGoroutine(cancel context.Context) { 154 | 155 | ticker := time.NewTicker(m.g.watchDogTimeout / 3) 156 | defer ticker.Stop() 157 | 158 | for { 159 | select { 160 | case <-ticker.C: 161 | renew, err := m.renewExpiration() 162 | if err != nil { 163 | return 164 | } 165 | // key not exists, so return goroutine 166 | if renew == 0 { 167 | m.cancelExpirationRenewal() 168 | return 169 | } 170 | case <-cancel.Done(): 171 | return 172 | } 173 | } 174 | } 175 | 176 | func (m *Mutex) renewExpiration() (int64, error) { 177 | result, err := m.g.c.Eval(context.TODO(), ` 178 | if (redis.call('exists', KEYS[1]) == 1) then 179 | redis.call('pexpire', KEYS[1], ARGV[1]); 180 | return 1; 181 | end; 182 | return 0; 183 | `, []string{m.Key}, m.g.watchDogTimeout.Milliseconds()).Result() 184 | if err != nil { 185 | return 0, err 186 | } 187 | if b, ok := result.(int64); ok { 188 | return b, nil 189 | } else { 190 | return 0, errors.Errorf("try lock result converter to int64 error, value is %v", result) 191 | } 192 | } 193 | 194 | func (m *Mutex) cancelExpirationRenewal() { 195 | if m.cancelFunc != nil { 196 | m.cancelFunc() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /mutex_test.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis/v8" 6 | "log" 7 | "sync" 8 | "sync/atomic" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func getGodisson() *Godisson { 14 | rdb := redis.NewClient(&redis.Options{ 15 | Addr: "localhost:6379", 16 | Password: "", // no password set 17 | DB: 0, // use default DB 18 | }) 19 | return NewGodisson(rdb) 20 | } 21 | 22 | func TestMutex_TryLock(t *testing.T) { 23 | g := getGodisson() 24 | mutex := g.NewMutex("hkn") 25 | 26 | t.Log(mutex.TryLock(-1, -1)) 27 | 28 | time.Sleep(30 * time.Second) 29 | mutex.Unlock() 30 | 31 | } 32 | 33 | func TestMutex_TryLock2(t *testing.T) { 34 | g := getGodisson() 35 | mutex := g.NewMutex("hkn") 36 | 37 | t.Log(mutex.TryLock(50000, -1)) 38 | 39 | time.Sleep(30 * time.Second) 40 | mutex.Unlock() 41 | 42 | } 43 | 44 | func singleLockUnlockTest(times int32, variableName string, g *Godisson) error { 45 | mutex := g.NewMutex("plus_" + variableName) 46 | a := 0 47 | wg := sync.WaitGroup{} 48 | total := int32(0) 49 | for i := int32(0); i < times; i++ { 50 | wg.Add(1) 51 | go func() { 52 | defer wg.Done() 53 | err := mutex.TryLock(1000, -1) 54 | if err != nil { 55 | return 56 | } 57 | a++ 58 | _, err = mutex.Unlock() 59 | if err != nil { 60 | panic("unlock failed") 61 | } 62 | atomic.AddInt32(&total, 1) 63 | }() 64 | } 65 | wg.Wait() 66 | log.Println(variableName, "=", a) 67 | if int32(a) != total { 68 | return fmt.Errorf("mutex lock and unlock test failed, %s shoule equal %d,but equal %d", variableName, total, a) 69 | } 70 | return nil 71 | } 72 | 73 | func TestMutex_LockUnlock(t *testing.T) { 74 | testCase := []int32{1, 10, 100, 1000, 10000} 75 | for _, v := range testCase { 76 | if err := singleLockUnlockTest(v, "variable_1", getGodisson()); err != nil { 77 | log.Fatalf("err=%v", err) 78 | } 79 | } 80 | } 81 | 82 | func TestMultiMutex(t *testing.T) { 83 | testCases := []int32{1, 10, 100, 1000} 84 | id := 0 85 | getId := func() int { 86 | id++ 87 | return id 88 | } 89 | for _, v := range testCases { 90 | wg := sync.WaitGroup{} 91 | numOfFailures := int32(0) 92 | for i := int32(0); i < v; i++ { 93 | wg.Add(1) 94 | go func() { 95 | defer wg.Done() 96 | err := singleLockUnlockTest(10, fmt.Sprintf("variable_%d", getId()), getGodisson()) 97 | if err != nil { 98 | t.Logf("test failed,err=%v", err) 99 | atomic.AddInt32(&numOfFailures, 1) 100 | return 101 | } 102 | }() 103 | wg.Wait() 104 | } 105 | if numOfFailures != 0 { 106 | t.Fatalf("multi mutex test failed, numOfFailures should equal 0,but equal %d", numOfFailures) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rlock.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/pkg/errors" 7 | "net" 8 | "time" 9 | ) 10 | 11 | var ErrLockNotObtained = errors.New("ErrLockNotObtained") 12 | 13 | const UNLOCK_MESSAGE int64 = 0 14 | 15 | const READ_UNLOCK_MESSAGE int64 = 1 16 | 17 | type RLock struct { 18 | Key string 19 | g *Godisson 20 | } 21 | 22 | var _ Locker = (*RLock)(nil) 23 | 24 | func newRLock(key string, g *Godisson) *RLock { 25 | return &RLock{Key: key, g: g} 26 | } 27 | 28 | func (r *RLock) Lock() error { 29 | return r.TryLock(-1, -1) 30 | } 31 | 32 | //TryLock try to obtain lock 33 | // waitTime, Millisecond 34 | // leaseTime, Millisecond, -1 enable watchdog 35 | func (r *RLock) TryLock(waitTime int64, leaseTime int64) error { 36 | wait := waitTime 37 | current := currentTimeMillis() 38 | ttl, err := r.tryAcquire(waitTime, leaseTime) 39 | if err != nil { 40 | return err 41 | } 42 | if ttl == 0 { 43 | return nil 44 | } 45 | wait -= currentTimeMillis() - current 46 | if wait <= 0 { 47 | return ErrLockNotObtained 48 | } 49 | current = currentTimeMillis() 50 | // PubSub 51 | sub := r.g.c.Subscribe(context.TODO(), r.g.getChannelName(r.Key)) 52 | defer sub.Close() 53 | timeoutCtx, timeoutCancel := context.WithTimeout(context.TODO(), time.Duration(wait)*time.Millisecond) 54 | defer timeoutCancel() 55 | _, err = sub.ReceiveMessage(timeoutCtx) 56 | if err != nil { 57 | return ErrLockNotObtained 58 | } 59 | 60 | wait -= currentTimeMillis() - current 61 | if wait <= 0 { 62 | return ErrLockNotObtained 63 | } 64 | 65 | for { 66 | currentTime := currentTimeMillis() 67 | ttl, err = r.tryAcquire(waitTime, leaseTime) 68 | if ttl == 0 { 69 | return nil 70 | } 71 | wait -= currentTimeMillis() - currentTime 72 | if wait <= 0 { 73 | return ErrLockNotObtained 74 | } 75 | currentTime = currentTimeMillis() 76 | 77 | var target *net.OpError 78 | if ttl >= 0 && ttl < wait { 79 | tCtx, _ := context.WithTimeout(context.TODO(), time.Duration(ttl)*time.Millisecond) 80 | _, err := sub.ReceiveMessage(tCtx) 81 | if err != nil { 82 | if errors.As(err, &target) { 83 | continue 84 | } 85 | } 86 | } else { 87 | tCtx, _ := context.WithTimeout(context.TODO(), time.Duration(wait)*time.Millisecond) 88 | _, err := sub.ReceiveMessage(tCtx) 89 | 90 | if err != nil { 91 | if errors.As(err, &target) { 92 | continue 93 | } 94 | } 95 | } 96 | wait -= currentTimeMillis() - currentTime 97 | if wait <= 0 { 98 | return ErrLockNotObtained 99 | } 100 | } 101 | } 102 | 103 | func (r *RLock) tryAcquire(waitTime int64, leaseTime int64) (int64, error) { 104 | goid, err := gid() 105 | if err != nil { 106 | return 0, err 107 | } 108 | if leaseTime != -1 { 109 | return r.tryAcquireInner(waitTime, leaseTime) 110 | } 111 | // watch dog 112 | ttl, err := r.tryAcquireInner(waitTime, r.g.watchDogTimeout.Milliseconds()) 113 | if err != nil { 114 | return 0, nil 115 | } 116 | if ttl == 0 { 117 | r.renewExpirationScheduler(goid) 118 | } 119 | return ttl, nil 120 | } 121 | 122 | func (r *RLock) renewExpirationScheduler(goroutineId uint64) { 123 | newEntry := NewRenewEntry() 124 | entryName := r.g.getEntryName(r.Key) 125 | if oldEntry, ok := r.g.RenewMap.Get(entryName); ok { 126 | oldEntry.(*RenewEntry).addGoroutineId(goroutineId) 127 | } else { 128 | newEntry.addGoroutineId(goroutineId) 129 | cancel, cancelFunc := context.WithCancel(context.TODO()) 130 | 131 | go r.renewExpirationSchedulerGoroutine(cancel, goroutineId) 132 | 133 | newEntry.cancelFunc = cancelFunc 134 | r.g.RenewMap.Set(entryName, newEntry) 135 | } 136 | 137 | } 138 | 139 | func (r *RLock) renewExpirationSchedulerGoroutine(cancel context.Context, goid uint64) { 140 | entryName := r.g.getEntryName(r.Key) 141 | 142 | ticker := time.NewTicker(r.g.watchDogTimeout / 3) 143 | defer ticker.Stop() 144 | for { 145 | select { 146 | case <-ticker.C: 147 | renew, err := r.renewExpiration(goid) 148 | if err != nil { 149 | r.g.RenewMap.Remove(entryName) 150 | return 151 | } 152 | // key not exists, so return goroutine 153 | if renew == 0 { 154 | r.cancelExpirationRenewal(0) 155 | return 156 | } 157 | case <-cancel.Done(): 158 | return 159 | } 160 | } 161 | } 162 | 163 | func (r *RLock) cancelExpirationRenewal(goid uint64) { 164 | entryName := r.g.getEntryName(r.Key) 165 | 166 | entry, ok := r.g.RenewMap.Get(entryName) 167 | if !ok { 168 | return 169 | } 170 | task := entry.(*RenewEntry) 171 | 172 | if goid != 0 { 173 | task.removeGoroutineId(goid) 174 | } 175 | if goid == 0 || task.hasNoThreads() { 176 | if task.cancelFunc != nil { 177 | task.cancelFunc() 178 | task.cancelFunc = nil 179 | } 180 | r.g.RenewMap.Remove(entryName) 181 | } 182 | } 183 | 184 | func (r *RLock) tryAcquireInner(waitTime int64, leaseTime int64) (int64, error) { 185 | gid, err := gid() 186 | if err != nil { 187 | return 0, err 188 | } 189 | lockName := r.getHashKey(gid) 190 | if err != nil { 191 | return 0, err 192 | } 193 | result, err := r.g.c.Eval(context.TODO(), ` 194 | if (redis.call('exists', KEYS[1]) == 0) then 195 | redis.call('hincrby', KEYS[1], ARGV[2], 1); 196 | redis.call('pexpire', KEYS[1], ARGV[1]); 197 | return 0; 198 | end; 199 | if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 200 | redis.call('hincrby', KEYS[1], ARGV[2], 1); 201 | redis.call('pexpire', KEYS[1], ARGV[1]); 202 | return 0; 203 | end; 204 | return redis.call('pttl', KEYS[1]); 205 | `, []string{r.Key}, leaseTime, lockName).Result() 206 | if err != nil { 207 | return 0, err 208 | } 209 | 210 | if b, ok := result.(int64); ok { 211 | return b, nil 212 | } else { 213 | return 0, errors.Errorf("tryAcquireInner result converter to int64 error, value is %v", result) 214 | } 215 | 216 | } 217 | 218 | func (r *RLock) Unlock() (int64, error) { 219 | goid, err := gid() 220 | if err != nil { 221 | return 0, err 222 | } 223 | 224 | defer func() { 225 | r.cancelExpirationRenewal(goid) 226 | }() 227 | 228 | result, err := r.g.c.Eval(context.Background(), ` 229 | if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then 230 | return nil; 231 | end; 232 | local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 233 | if (counter > 0) then 234 | redis.call('pexpire', KEYS[1], ARGV[2]); 235 | return 0; 236 | else 237 | redis.call('del', KEYS[1]); 238 | redis.call('publish', KEYS[2], ARGV[1]); 239 | return 1; 240 | end; 241 | return nil; 242 | `, []string{r.Key, r.g.getChannelName(r.Key)}, UNLOCK_MESSAGE, DefaultWatchDogTimeout, r.getHashKey(goid)).Result() 243 | if err != nil { 244 | return 0, err 245 | } 246 | if b, ok := result.(int64); ok { 247 | return b, nil 248 | } else { 249 | return 0, errors.Errorf("try lock result converter to bool error, value is %v", result) 250 | } 251 | } 252 | 253 | func (r *RLock) renewExpiration(gid uint64) (int64, error) { 254 | result, err := r.g.c.Eval(context.TODO(), ` 255 | if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 256 | redis.call('pexpire', KEYS[1], ARGV[1]); 257 | return 1; 258 | end ; 259 | return 0 260 | `, []string{r.Key}, r.g.watchDogTimeout.Milliseconds(), r.getHashKey(gid)).Result() 261 | if err != nil { 262 | return 0, err 263 | } 264 | if b, ok := result.(int64); ok { 265 | return b, nil 266 | } else { 267 | return 0, errors.Errorf("try lock result converter to int64 error, value is %v", result) 268 | } 269 | } 270 | 271 | func (r *RLock) getHashKey(goroutineId uint64) string { 272 | return fmt.Sprintf("%s:%d", r.g.uuid, goroutineId) 273 | } 274 | -------------------------------------------------------------------------------- /rlock_test.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "github.com/pkg/errors" 8 | "net" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestNewGedisson(t *testing.T) { 14 | rdb := redis.NewClient(&redis.Options{ 15 | Addr: "localhost:6379", 16 | Password: "", // no password set 17 | DB: 0, // use default DB 18 | }) 19 | 20 | gedisson := NewGodisson(rdb) 21 | lock := gedisson.NewRLock("hkn") 22 | t.Log(lock.TryLock(20000, 40000)) 23 | time.Sleep(10 * time.Second) 24 | lock.Unlock() 25 | 26 | //time.Sleep(100 * time.Second) 27 | } 28 | 29 | func TestNewGedisson1(t *testing.T) { 30 | rdb := redis.NewClient(&redis.Options{ 31 | Addr: "localhost:6379", 32 | Password: "", // no password set 33 | DB: 0, // use default DB 34 | }) 35 | 36 | gedisson := NewGodisson(rdb) 37 | lock1 := gedisson.NewRLock("hkn") 38 | 39 | t.Log(lock1.TryLock(20000, 40000)) 40 | time.Sleep(10 * time.Second) 41 | lock1.Unlock() 42 | 43 | } 44 | 45 | func TestNewGedisson2(t *testing.T) { 46 | rdb := redis.NewClient(&redis.Options{ 47 | Addr: "localhost:6379", 48 | Password: "", // no password set 49 | DB: 0, // use default DB 50 | }) 51 | 52 | gedisson := NewGodisson(rdb) 53 | lock1 := gedisson.NewRLock("hkn") 54 | 55 | t.Log(lock1.TryLock(20000, 40000)) 56 | time.Sleep(10 * time.Second) 57 | lock1.Unlock() 58 | 59 | } 60 | func TestNewGedissonWatchdog(t *testing.T) { 61 | rdb := redis.NewClient(&redis.Options{ 62 | Addr: "localhost:6379", 63 | Password: "", // no password set 64 | DB: 0, // use default DB 65 | }) 66 | 67 | gedisson := NewGodisson(rdb) 68 | 69 | go func() { 70 | ticker := time.NewTicker(2 * time.Second) 71 | defer ticker.Stop() 72 | for { 73 | select { 74 | case <-ticker.C: 75 | for _, i := range gedisson.RenewMap.Items() { 76 | e := i.(*RenewEntry) 77 | fmt.Printf("goids %v", e.goroutineIds) 78 | } 79 | } 80 | 81 | } 82 | }() 83 | 84 | lock := gedisson.NewRLock("hkn") 85 | t.Log(lock.TryLock(40000, -1)) 86 | 87 | lock1 := gedisson.NewRLock("hkn") 88 | t.Log(lock1.TryLock(40000, -1)) 89 | time.Sleep(1 * time.Minute) 90 | lock1.Unlock() 91 | 92 | time.Sleep(30 * time.Second) 93 | lock.Unlock() 94 | 95 | } 96 | 97 | func TestSub(t *testing.T) { 98 | rdb := redis.NewClient(&redis.Options{ 99 | Addr: "localhost:6379", 100 | Password: "", // no password set 101 | DB: 0, // use default DB 102 | }) 103 | sub := rdb.Subscribe(context.Background(), "123-channel") 104 | ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second) 105 | defer cancelFunc() 106 | 107 | message, err := sub.ReceiveMessage(ctx) 108 | var target *net.OpError 109 | t.Log(message, err, errors.As(err, &target)) 110 | } 111 | 112 | func TestPub(t *testing.T) { 113 | rdb := redis.NewClient(&redis.Options{ 114 | Addr: "localhost:6379", 115 | Password: "", // no password set 116 | DB: 0, // use default DB 117 | }) 118 | 119 | rdb.Publish(context.Background(), "123-channel", "123") 120 | 121 | } 122 | 123 | func TestReLock(t *testing.T) { 124 | rdb := redis.NewClient(&redis.Options{ 125 | Addr: "localhost:6379", 126 | Password: "", // no password set 127 | DB: 0, // use default DB 128 | }) 129 | 130 | g := NewGodisson(rdb) 131 | 132 | lock1 := g.NewRLock("hkn") 133 | t.Log(lock1.TryLock(-1, 30000)) 134 | time.Sleep(10 * time.Second) 135 | 136 | lock2 := g.NewRLock("hkn") 137 | t.Log(lock2.TryLock(-1, -1)) 138 | 139 | } 140 | -------------------------------------------------------------------------------- /rwlock.go: -------------------------------------------------------------------------------- 1 | package godisson 2 | --------------------------------------------------------------------------------