-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package cache | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/gomodule/redigo/redis" | ||
) | ||
|
||
// ErrLockMismatch is the error if the key is locked by someone else | ||
var ErrLockMismatch = errors.New("key is locked with a different secret") | ||
|
||
// lockScript is the locking script | ||
const lockScript = ` | ||
local v = redis.call("GET", KEYS[1]) | ||
if v == false or v == ARGV[1] | ||
then | ||
return redis.call("SET", KEYS[1], ARGV[1], "EX", ARGV[2]) and 1 | ||
else | ||
return 0 | ||
end | ||
` | ||
|
||
// unlockScript is the unlocking script | ||
const unlockScript = ` | ||
local v = redis.call("GET",KEYS[1]) | ||
if v == false then | ||
return 1 | ||
elseif v == ARGV[1] then | ||
return redis.call("DEL",KEYS[1]) | ||
else | ||
return 0 | ||
end | ||
` | ||
|
||
// WriteLock attempts to grab a redis lock. | ||
func WriteLock(name, secret string, ttl int64) (locked bool, err error) { | ||
|
||
// Create a new connection and defer closing | ||
conn := GetConnection() | ||
defer func() { | ||
_ = conn.Close() | ||
}() | ||
|
||
script := redis.NewScript(1, lockScript) | ||
var resp int | ||
resp, err = redis.Int(script.Do(conn, name, secret, ttl)) | ||
if err != nil { | ||
return | ||
} else if resp != 0 { | ||
locked = true | ||
return | ||
} | ||
err = ErrLockMismatch | ||
return | ||
} | ||
|
||
// ReleaseLock releases the redis lock | ||
func ReleaseLock(name, secret string) (released bool, err error) { | ||
|
||
// Create a new connection and defer closing | ||
conn := GetConnection() | ||
defer func() { | ||
_ = conn.Close() | ||
}() | ||
|
||
script := redis.NewScript(1, unlockScript) | ||
var resp int | ||
resp, err = redis.Int(script.Do(conn, name, secret)) | ||
if err != nil { | ||
return | ||
} else if resp != 0 { | ||
released = true | ||
return | ||
} | ||
err = ErrLockMismatch | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package cache | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
// TestWriteLock will run basic tests for lock/release | ||
func TestWriteLock(t *testing.T) { | ||
|
||
// Create a local connection | ||
if err := startTest(); err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
|
||
// Disconnect at end | ||
defer endTest() | ||
|
||
// attempt to lock | ||
locked, err := WriteLock("my-key", "the-secret", int64(10)) | ||
if err != nil { | ||
t.Fatalf("error acquiring lock: %q", err.Error()) | ||
} | ||
if !locked { | ||
t.Fatal("expected WriteLock to return true") | ||
} | ||
|
||
// attempt to re-lock (should succeed) | ||
locked, err = WriteLock("my-key", "the-secret", int64(5)) | ||
if !locked || err != nil { | ||
t.Fatalf("expected re-lock attempt to succeed, got locked %t error %q", locked, err) | ||
} | ||
|
||
// attempt to re-lock with different secret (should return error) | ||
locked, err = WriteLock("my-key", "the-different-secret", int64(5)) | ||
if locked || err != ErrLockMismatch { | ||
t.Fatalf("expected re-lock attempt to fail, got locked %t error %q", locked, err) | ||
} | ||
|
||
// attempt to release lock w/ bad secret | ||
var unlocked bool | ||
unlocked, err = ReleaseLock("my-key", "the-wrong-secret") | ||
if unlocked || err != ErrLockMismatch { | ||
t.Fatalf("expected release lock w/ bad secret to fail, got unlocked %t error %q", unlocked, err) | ||
} | ||
|
||
// attempt to release lock w/ correct secret | ||
unlocked, err = ReleaseLock("my-key", "the-secret") | ||
if !unlocked || err != nil { | ||
t.Fatalf("expected release lock to succeed, got unlocked %t error %q", unlocked, err) | ||
} | ||
|
||
// attempt to release lock again (should return true, nil) | ||
unlocked, err = ReleaseLock("myKey", "the-secret") | ||
if !unlocked || err != nil { | ||
t.Fatalf("expected repeat release lock to succeed, got unlocked %t error %q", unlocked, err) | ||
} | ||
} | ||
|
||
// TestReleaseLock will run basic tests for lock/release | ||
func TestReleaseLock(t *testing.T) { | ||
|
||
// Create a local connection | ||
if err := startTest(); err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
|
||
// Disconnect at end | ||
defer endTest() | ||
|
||
// attempt to lock | ||
locked, err := WriteLock("my-key", "the-secret", int64(5)) | ||
if err != nil { | ||
t.Fatalf("error acquiring lock: %q", err.Error()) | ||
} | ||
if !locked { | ||
t.Fatal("expected WriteLock to return true") | ||
} | ||
|
||
time.Sleep(50 * time.Millisecond) | ||
|
||
// test if lock is there | ||
locked, err = WriteLock("my-key", "the-different-secret", int64(5)) | ||
if locked || err != ErrLockMismatch { | ||
t.Fatalf("expected lock attempt to fail, got locked %t error %q", locked, err) | ||
} | ||
|
||
time.Sleep(50 * time.Millisecond) | ||
|
||
// test if lock is there | ||
locked, err = WriteLock("my-key", "the-different-secret", int64(5)) | ||
if locked || err != ErrLockMismatch { | ||
t.Fatalf("expected lock attempt to fail, got locked %t error %q", locked, err) | ||
} | ||
|
||
// attempt to release lock w/ correct secret | ||
var unlocked bool | ||
unlocked, err = ReleaseLock("my-key", "the-secret") | ||
if !unlocked || err != nil { | ||
t.Fatalf("expected release lock to succeed, got unlocked %t error %q", unlocked, err) | ||
} | ||
} |