2014-09-09 13:40:41 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"bitbucket.org/ww/cabinet"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
stateMetaKey = "state"
|
|
|
|
stateSaveInterval = 1 // seconds
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
Storage struct {
|
|
|
|
kyoto *cabinet.KCDB
|
|
|
|
counters map[string]*counter
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func New(path string) (s *Storage, err error) {
|
|
|
|
kyoto := cabinet.New()
|
|
|
|
if err = kyoto.Open(path, cabinet.KCOWRITER|cabinet.KCOCREATE); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s = &Storage{
|
|
|
|
kyoto: kyoto,
|
|
|
|
counters: make(map[string]*counter),
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-10 12:52:03 +00:00
|
|
|
func (s *Storage) Get(queue string) (message []byte, ok bool) {
|
2014-09-11 10:27:34 +00:00
|
|
|
if _, exist := s.counters[queue]; !exist {
|
2014-09-10 12:52:03 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if size := s.counters[queue].distance(); size == 0 {
|
|
|
|
return
|
2014-09-09 13:40:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var index uint
|
|
|
|
select {
|
|
|
|
case index = <-s.counters[queue].stream:
|
2014-09-10 12:52:03 +00:00
|
|
|
default:
|
2014-09-09 13:40:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
key := makeKey(queue, index)
|
2014-09-11 10:27:34 +00:00
|
|
|
message, err := s.kyoto.Get(key)
|
|
|
|
if err != nil {
|
2014-09-10 12:52:03 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
if err := s.kyoto.Remove(key); err != nil {
|
|
|
|
panic(err)
|
2014-09-09 13:40:41 +00:00
|
|
|
}
|
2014-09-11 10:27:34 +00:00
|
|
|
ok = true
|
2014-09-09 13:40:41 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) Put(queue string, message []byte) (err error) {
|
|
|
|
if _, ok := s.counters[queue]; !ok {
|
|
|
|
s.counters[queue] = newCounter(0, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.counters[queue].tryWrite(func(index uint) bool {
|
|
|
|
key := makeKey(queue, index)
|
|
|
|
err = s.kyoto.Set(key, message)
|
|
|
|
|
|
|
|
return (err == nil)
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-09-11 19:00:08 +00:00
|
|
|
func (s *Storage) Info() map[string]uint {
|
|
|
|
info := make(map[string]uint)
|
|
|
|
|
|
|
|
for queue, c := range s.counters {
|
|
|
|
info[queue] = c.distance()
|
|
|
|
}
|
|
|
|
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
2014-09-09 13:40:41 +00:00
|
|
|
func (s *Storage) Close() (err error) {
|
|
|
|
if err = s.kyoto.Sync(true); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = s.kyoto.Close()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// State
|
|
|
|
|
|
|
|
func (s *Storage) saveState() (err error) {
|
|
|
|
state := make(map[string]map[string]uint)
|
|
|
|
for queue, ctr := range s.counters {
|
|
|
|
state[queue] = map[string]uint{
|
|
|
|
"wi": ctr.write,
|
|
|
|
"ri": ctr.read,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jsn, _ := json.Marshal(state)
|
|
|
|
err = s.kyoto.Set([]byte(stateMetaKey), jsn)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) loadState() (err error) {
|
|
|
|
var (
|
|
|
|
jsn []byte
|
|
|
|
state = make(map[string]map[string]uint)
|
|
|
|
)
|
|
|
|
|
|
|
|
if jsn, err = s.kyoto.Get([]byte(stateMetaKey)); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = json.Unmarshal(jsn, &state); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for queue, meta := range state {
|
|
|
|
s.counters[queue] = newCounter(meta["wi"], meta["ri"])
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Storage) keepStatePersisted() {
|
|
|
|
t := time.NewTicker(stateSaveInterval * time.Second)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-t.C:
|
|
|
|
if err := s.saveState(); err != nil {
|
|
|
|
panic("Failed to persist state")
|
|
|
|
}
|
|
|
|
if err := s.kyoto.Sync(false); err != nil {
|
|
|
|
panic("Failed to sync storage")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeKey(queue string, index uint) []byte {
|
2014-09-11 19:00:08 +00:00
|
|
|
// TODO: There should be a faster way
|
2014-09-09 13:40:41 +00:00
|
|
|
return []byte(strings.Join([]string{queue, strconv.FormatUint(uint64(index), 10)}, "_"))
|
|
|
|
}
|