Skip to content

Instantly share code, notes, and snippets.

@vaskoz
Last active November 2, 2017 02:32
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 vaskoz/757e2f4ec03514726f39b326185b7069 to your computer and use it in GitHub Desktop.
Save vaskoz/757e2f4ec03514726f39b326185b7069 to your computer and use it in GitHub Desktop.
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
}
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()
}
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