Last active
December 29, 2020 19:31
-
-
Save polynomialspace/e16ae52a9e460c727dd4afd5d065f8a7 to your computer and use it in GitHub Desktop.
WIP-y POC emulating the actor model in go. This snippet is intended to be implemented later in a larger project with a specific usecase after further modification.
This file contains hidden or 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
package main | |
import ( | |
"testing" | |
"time" | |
) | |
type TokenRequest struct { | |
Key string | |
Value string | |
Ok bool | |
Done chan struct{} | |
} | |
// NewTokenActor will generate a get/set pair of functions to interact with a | |
// map[string]string intended for a set of token:username mappings. Emulating | |
// the actor model tidies up needing to embed the map in multiple objects or | |
// having a global-scoped sync.Map. | |
// In this case a Value:"" is never valid so we use that to differentiate | |
// a Load/Store op although there should be error handling if we try to | |
// TokenSet("foo", "") | |
func NewTokenActor(expiry time.Duration) (func(key string) (string, bool), func(key, value string) bool) { | |
ch := make(chan *TokenRequest) | |
go func() { | |
tokens := make(map[string]string) // map[token]username | |
for req := range ch { | |
switch req.Value { | |
case "": | |
req.Value, req.Ok = tokens[req.Key] | |
delete(tokens, req.Key) | |
default: | |
_, req.Ok = tokens[req.Key] | |
req.Ok = !req.Ok | |
if req.Ok { | |
tokens[req.Key] = req.Value | |
} | |
go func(key string) { | |
time.Sleep(expiry * time.Second) | |
delete(tokens, key) | |
}(req.Key) | |
} | |
close(req.Done) | |
} | |
}() | |
return func(key string) (string, bool) { | |
req := &TokenRequest{ | |
Key: key, | |
Done: make(chan struct{}), | |
} | |
ch <- req | |
<-req.Done | |
return req.Value, req.Ok | |
}, | |
func(key, value string) bool { | |
req := &TokenRequest{ | |
Key: key, | |
Value: value, | |
Done: make(chan struct{}), | |
} | |
ch <- req | |
<-req.Done | |
return req.Ok | |
} | |
} | |
var testKey, testVal = "foo", "bar" | |
func TestGetWithoutSet(t *testing.T) { | |
TokenGet, _ := NewTokenActor(3) | |
res, ok := TokenGet(testKey) | |
if ok || res != "" { | |
t.Errorf("Expected {ok:false, res:\"\"}, got: {ok:%v, res:\"%v\"}", ok, res) | |
} | |
} | |
func TestGetAfterSet(t *testing.T) { | |
TokenGet, TokenSet := NewTokenActor(3) | |
ok := TokenSet(testKey, testVal) | |
// we shouldn't even need to test this bit | |
if ok != true { | |
t.Error("Set on empty returned {ok:false}") | |
} | |
res, ok := TokenGet(testKey) | |
if !ok || res != testVal { | |
t.Errorf("Expected {ok:false, res:\"%v\"}, got: {ok:%v, res:\"%v\"}", testVal, ok, res) | |
} | |
} | |
func TestGetAfterSetAndGet(t *testing.T) { | |
TokenGet, TokenSet := NewTokenActor(3) | |
TokenSet(testKey, testVal) | |
TokenGet(testKey) | |
// After the first Get the token should expire | |
res, ok := TokenGet(testKey) | |
if ok || res != "" { | |
t.Errorf("Expected {ok:false, res:\"\"}, got: {ok:%v, res:\"%v\"}", ok, res) | |
} | |
} | |
func TestGetAfterSetAndTimeoutExpired(t *testing.T) { | |
TokenGet, TokenSet := NewTokenActor(3) | |
TokenSet(testKey, testVal) | |
// Timing isnt exact when dealing with goroutines and in this case | |
// +/- a few seconds isn't critical. | |
time.Sleep(4 * time.Second) | |
// After a timeout of 3 seconds Get on the token should expire | |
res, ok := TokenGet(testKey) | |
if ok || res != "" { | |
t.Errorf("Expected {ok:false, res:\"\"}, got: {ok:%v, res:\"%v\"}", ok, res) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment