Skip to content

Instantly share code, notes, and snippets.

@sidazhang
Last active August 29, 2015 13:56
Show Gist options
  • Save sidazhang/9100294 to your computer and use it in GitHub Desktop.
Save sidazhang/9100294 to your computer and use it in GitHub Desktop.
Semaphore that times out if it is not unlocked within a time frame
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type Empty struct {
}
type Glock struct {
Locks map[string]*Lock
LocksLock sync.RWMutex
}
func NewGlock() *Glock {
return &Glock{make(map[string]*Lock), sync.RWMutex{}}
}
// lock and check if a matching lock exists
// if it does return it
// if not create a new lock
// note: matching lock requires the same key and the same size; if size is different, it will be
// replaced
func (g *Glock) GetLock(key string, size int) *Lock {
g.LocksLock.RLock()
l, ok := g.Locks[key]
g.LocksLock.RUnlock()
// in most cases a lock already exists so we use a read lock
// to confirm that
if ok && cap(l.Semaphore) == size {
return l
} else {
// in this case a lock does not exist
// let's acquire write lock and check again and see if someone else created one already
g.LocksLock.Lock()
if l, ok := g.Locks[key]; ok && cap(l.Semaphore) == size {
// someone created one in the meantime, let's return that
return l
} else {
// lock still does not exist; let's create one
l := &Lock{make(chan Empty, size), make(map[int64]bool), int64(1), sync.Mutex{}}
g.Locks[key] = l
return l
}
g.LocksLock.Unlock()
}
return nil
}
// note: no overflow on int64 - we will probably never get to the end of int64
type Lock struct {
Semaphore chan Empty
TimeoutSet map[int64]bool
Id int64
TimeoutSetLock sync.Mutex
}
// increment id
func (b *Lock) Lock() int64 {
id := atomic.AddInt64(&b.Id, 1)
b.TimeoutSetLock.Lock()
// increment the id each time
// impossible to get a collision
// so we don't need to check whether key exists
b.TimeoutSet[id] = true
b.TimeoutSetLock.Unlock()
time.AfterFunc(1000*time.Millisecond, func() {
b.Unlock(id)
})
return id
}
// Blocking lock
func (b *Lock) BLock() int64 {
b.Semaphore <- Empty{}
return b.Lock()
}
// Non Blocking lock
func (b *Lock) NLock() int64 {
select {
case b.Semaphore <- Empty{}:
return b.Lock()
// it is full - move on
default:
// we initialize each lock at 1
// id 0 means that the lock is full
return int64(0)
}
return int64(0)
}
func (b *Lock) Unlock(id int64) {
b.TimeoutSetLock.Lock()
n := len(b.TimeoutSet)
delete(b.TimeoutSet, id)
deleted := n != len(b.TimeoutSet)
b.TimeoutSetLock.Unlock()
if deleted {
<-b.Semaphore
}
}
func main() {
g := NewGlock()
l := g.GetLock("SomeKey", 2)
for i := 0; i < 10; i++ {
k := l.BLock()
fmt.Println(k)
}
// now let's wait all of these locks to timeout
time.Sleep(2 * time.Second)
// we are now starting fresh
for i := 0; i < 10; i++ {
k := l.NLock()
if k == int64(0) {
fmt.Println("It is full")
}
}
fmt.Println("done")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment