Skip to content

Instantly share code, notes, and snippets.

@polynomialspace
Last active December 29, 2020 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save polynomialspace/e16ae52a9e460c727dd4afd5d065f8a7 to your computer and use it in GitHub Desktop.
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.
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