Last active
November 2, 2017 02:32
-
-
Save vaskoz/757e2f4ec03514726f39b326185b7069 to your computer and use it in GitHub Desktop.
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
package main | |
import ( | |
"fmt" | |
"sync" | |
) | |
type Connection interface { | |
Send([]byte) error | |
Release() | |
} | |
type Cache interface { | |
Acquire() (Connection, error) | |
Release(Connection) error | |
} | |
type connection struct { | |
sync.RWMutex | |
cache *connectionCache // cache this connection belongs to | |
conn *int // let's say it's FD | |
} | |
func (c *connection) Send(data []byte) error { | |
c.RLock() | |
defer c.RUnlock() | |
if c.conn != nil { | |
_ = data // send it | |
return nil | |
} | |
return fmt.Errorf("connection has been closed") | |
} | |
func (c *connection) Release() { | |
c.Lock() | |
defer c.Unlock() | |
_ = c.cache.Release(c) | |
c.conn = nil | |
} | |
type connectionCache struct { | |
sync.RWMutex | |
available []*connection | |
inUse []*connection | |
} | |
func (cc *connectionCache) Acquire() (Connection, error) { | |
cc.Lock() | |
defer cc.Unlock() | |
if len(cc.available) == 0 { | |
return nil, fmt.Errorf("no available connection at this time") | |
} | |
conn := cc.available[len(cc.available)-1] | |
cc.available = cc.available[:len(cc.available)-1] | |
cc.inUse = append(cc.inUse, conn) | |
return conn, nil | |
} | |
func (cc *connectionCache) Release(c Connection) error { | |
cc.Lock() | |
defer cc.Unlock() | |
for i, inUse := range cc.inUse { | |
if inUse == c { | |
cc.available = append(cc.available, inUse) | |
// https://github.com/golang/go/wiki/SliceTricks | |
copy(cc.inUse[i:], cc.inUse[i+1:]) | |
cc.inUse[len(cc.inUse)-1] = nil | |
cc.inUse = cc.inUse[:len(cc.inUse)-1] | |
return nil | |
} | |
} | |
return fmt.Errorf("Couldn't find connection in use") | |
} | |
func NewCache(initialSize int) Cache { | |
c := &connectionCache{available: make([]*connection, initialSize), inUse: make([]*connection, 0)} | |
for i := 0; i < initialSize; i++ { | |
c.available[i] = &connection{cache: c, conn: &i} | |
} | |
return c | |
} |
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
package main | |
import ( | |
"sync" | |
"testing" | |
) | |
func TestCacheAcquireNormal(t *testing.T) { | |
t.Parallel() | |
size := 5 | |
cache := NewCache(size) | |
connections := make([]Connection, size) | |
for i := range connections { | |
var err error | |
connections[i], err = cache.Acquire() | |
if err != nil { | |
t.Error("expected no errors while acquiring the total number of connections") | |
} | |
} | |
} | |
func TestCacheAcquireNormalConcurrently(t *testing.T) { | |
t.Parallel() | |
size := 5 | |
cache := NewCache(size) | |
var wg sync.WaitGroup | |
wg.Add(size) | |
for i := 0; i < size; i++ { | |
go func() { | |
conn, err := cache.Acquire() | |
if conn == nil { | |
t.Errorf("Connection shouldn't be nil") | |
} | |
if err != nil { | |
t.Errorf("No error should be given but got %v", err) | |
} | |
wg.Done() | |
}() | |
} | |
wg.Wait() | |
} | |
func TestCacheAcquireTooMany(t *testing.T) { | |
t.Parallel() | |
size := 5 | |
cache := NewCache(size) | |
for i := 0; i < size; i++ { | |
conn, err := cache.Acquire() | |
if conn == nil { | |
t.Errorf("Connection shouldn't be nil") | |
} | |
if err != nil { | |
t.Errorf("No error should be given but got %v", err) | |
} | |
} | |
conn, err := cache.Acquire() | |
if conn != nil { | |
t.Errorf("We should not have a connection") | |
} | |
if err == nil { | |
t.Errorf("I was expecting an error because we're out of connections") | |
} | |
} | |
func TestCacheAcquireAndReleaseNormal(t *testing.T) { | |
t.Parallel() | |
size := 5 | |
cache := NewCache(size) | |
connections := make([]Connection, size) | |
for i := range connections { | |
var err error | |
connections[i], err = cache.Acquire() | |
if err != nil { | |
t.Error("expected no errors while acquiring the total number of connections") | |
} | |
} | |
for _, c := range connections { | |
if err := c.Send([]byte{}); err != nil { | |
t.Errorf("no send error expected by got %v", err) | |
} | |
} | |
for _, c := range connections { | |
c.Release() | |
} | |
} | |
func TestCacheAcquireAndReleaseThenSend(t *testing.T) { | |
t.Parallel() | |
size := 1 | |
cache := NewCache(size) | |
connections := make([]Connection, size) | |
for i := range connections { | |
var err error | |
connections[i], err = cache.Acquire() | |
if err != nil { | |
t.Error("expected no errors while acquiring the total number of connections") | |
} | |
} | |
for _, c := range connections { | |
c.Release() | |
} | |
for _, c := range connections { | |
if err := c.Send([]byte{}); err == nil { | |
t.Errorf("send error expected") | |
} | |
} | |
} | |
func TestReleaseConnectionNotInCache(t *testing.T) { | |
t.Parallel() | |
size := 1 | |
cache := NewCache(size) | |
connections := make([]Connection, size) | |
for i := range connections { | |
var err error | |
connections[i], err = cache.Acquire() | |
if err != nil { | |
t.Error("expected no errors while acquiring the total number of connections") | |
} | |
} | |
fakeConnection := &connection{} | |
if err := cache.Release(fakeConnection); err == nil { | |
t.Errorf("Should not be able to release a fake connection") | |
} | |
} | |
func TestCacheAcquireSendAndReleaseConcurrently(t *testing.T) { | |
t.Parallel() | |
size := 10 | |
cache := NewCache(size) | |
goroutines := 1000 | |
var wg sync.WaitGroup | |
wg.Add(goroutines) | |
for i := 0; i < goroutines; i++ { | |
go func() { | |
var conn Connection | |
var err error | |
if conn, err = cache.Acquire(); err == nil { | |
conn.Send([]byte{}) | |
if releaseErr := cache.Release(conn); releaseErr != nil { | |
t.Error("This connection should release successfully") | |
} | |
} | |
wg.Done() | |
}() | |
} | |
wg.Wait() | |
} |
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
time go test -cover -race -v . | |
=== RUN TestCacheAcquireNormal | |
=== PAUSE TestCacheAcquireNormal | |
=== RUN TestCacheAcquireNormalConcurrently | |
=== PAUSE TestCacheAcquireNormalConcurrently | |
=== RUN TestCacheAcquireTooMany | |
=== PAUSE TestCacheAcquireTooMany | |
=== RUN TestCacheAcquireAndReleaseNormal | |
=== PAUSE TestCacheAcquireAndReleaseNormal | |
=== RUN TestCacheAcquireAndReleaseThenSend | |
=== PAUSE TestCacheAcquireAndReleaseThenSend | |
=== RUN TestReleaseConnectionNotInCache | |
=== PAUSE TestReleaseConnectionNotInCache | |
=== RUN TestCacheAcquireSendAndReleaseConcurrently | |
=== PAUSE TestCacheAcquireSendAndReleaseConcurrently | |
=== CONT TestCacheAcquireNormal | |
=== CONT TestCacheAcquireAndReleaseNormal | |
--- PASS: TestCacheAcquireNormal (0.00s) | |
=== CONT TestCacheAcquireTooMany | |
--- PASS: TestCacheAcquireAndReleaseNormal (0.00s) | |
=== CONT TestCacheAcquireAndReleaseThenSend | |
=== CONT TestCacheAcquireNormalConcurrently | |
--- PASS: TestCacheAcquireAndReleaseThenSend (0.00s) | |
--- PASS: TestCacheAcquireTooMany (0.00s) | |
=== CONT TestReleaseConnectionNotInCache | |
=== CONT TestCacheAcquireSendAndReleaseConcurrently | |
--- PASS: TestReleaseConnectionNotInCache (0.00s) | |
--- PASS: TestCacheAcquireNormalConcurrently (0.00s) | |
--- PASS: TestCacheAcquireSendAndReleaseConcurrently (0.05s) | |
PASS | |
coverage: 100.0% of statements | |
ok _/Users/vaskozdravevski/tmp 1.096s | |
real 0m3.903s | |
user 0m5.955s | |
sys 0m1.280s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment