mirror of https://github.com/tidwall/tile38.git
155 lines
3.2 KiB
Go
155 lines
3.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"log"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/tidwall/btree"
|
|
"github.com/tidwall/resp"
|
|
"github.com/tidwall/tile38/internal/server"
|
|
)
|
|
|
|
type exitem struct {
|
|
key, id string
|
|
at time.Time
|
|
}
|
|
|
|
func (a *exitem) Less(v btree.Item, ctx interface{}) bool {
|
|
b := v.(*exitem)
|
|
if a.at.Before(b.at) {
|
|
return true
|
|
}
|
|
if a.at.After(b.at) {
|
|
return false
|
|
}
|
|
if a.key < b.key {
|
|
return true
|
|
}
|
|
if a.key > b.key {
|
|
return false
|
|
}
|
|
return a.id < b.id
|
|
}
|
|
|
|
// fillExpiresList occurs once at startup
|
|
func (c *Controller) fillExpiresList() {
|
|
c.exlistmu.Lock()
|
|
c.exlist = c.exlist[:0]
|
|
for key, m := range c.expires {
|
|
for id, at := range m {
|
|
c.exlist = append(c.exlist, exitem{key, id, at})
|
|
}
|
|
}
|
|
c.exlistmu.Unlock()
|
|
}
|
|
|
|
// clearIDExpires clears a single item from the expires list.
|
|
func (c *Controller) clearIDExpires(key, id string) (cleared bool) {
|
|
if len(c.expires) == 0 {
|
|
return false
|
|
}
|
|
m, ok := c.expires[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
_, ok = m[id]
|
|
if !ok {
|
|
return false
|
|
}
|
|
delete(m, id)
|
|
return true
|
|
}
|
|
|
|
// clearKeyExpires clears all items that are marked as expires from a single key.
|
|
func (c *Controller) clearKeyExpires(key string) {
|
|
delete(c.expires, key)
|
|
}
|
|
|
|
// expireAt marks an item as expires at a specific time.
|
|
func (c *Controller) expireAt(key, id string, at time.Time) {
|
|
m := c.expires[key]
|
|
if m == nil {
|
|
m = make(map[string]time.Time)
|
|
c.expires[key] = m
|
|
}
|
|
m[id] = at
|
|
c.exlistmu.Lock()
|
|
c.exlist = append(c.exlist, exitem{key, id, at})
|
|
c.exlistmu.Unlock()
|
|
}
|
|
|
|
// getExpires returns the when an item expires.
|
|
func (c *Controller) getExpires(key, id string) (at time.Time, ok bool) {
|
|
if len(c.expires) == 0 {
|
|
return at, false
|
|
}
|
|
m, ok := c.expires[key]
|
|
if !ok {
|
|
return at, false
|
|
}
|
|
at, ok = m[id]
|
|
return at, ok
|
|
}
|
|
|
|
// hasExpired returns true if an item has expired.
|
|
func (c *Controller) hasExpired(key, id string) bool {
|
|
at, ok := c.getExpires(key, id)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return time.Now().After(at)
|
|
}
|
|
|
|
// backgroundExpiring watches for when items that have expired must be purged
|
|
// from the database. It's executes 10 times a seconds.
|
|
func (c *Controller) backgroundExpiring() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
var purgelist []exitem
|
|
for {
|
|
if c.stopBackgroundExpiring.on() {
|
|
return
|
|
}
|
|
now := time.Now()
|
|
purgelist = purgelist[:0]
|
|
c.exlistmu.Lock()
|
|
for i := 0; i < 20 && len(c.exlist) > 0; i++ {
|
|
ix := rand.Int() % len(c.exlist)
|
|
if now.After(c.exlist[ix].at) {
|
|
// purge from exlist
|
|
purgelist = append(purgelist, c.exlist[ix])
|
|
c.exlist[ix] = c.exlist[len(c.exlist)-1]
|
|
c.exlist = c.exlist[:len(c.exlist)-1]
|
|
}
|
|
}
|
|
c.exlistmu.Unlock()
|
|
if len(purgelist) > 0 {
|
|
c.mu.Lock()
|
|
for _, item := range purgelist {
|
|
if c.hasExpired(item.key, item.id) {
|
|
// purge from database
|
|
msg := &server.Message{}
|
|
msg.Values = resp.MultiBulkValue("del", item.key, item.id).Array()
|
|
msg.Command = "del"
|
|
_, d, err := c.cmdDel(msg)
|
|
if err != nil {
|
|
c.mu.Unlock()
|
|
log.Fatal(err)
|
|
continue
|
|
}
|
|
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
|
|
c.mu.Unlock()
|
|
log.Fatal(err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
c.mu.Unlock()
|
|
if len(purgelist) > 5 {
|
|
continue
|
|
}
|
|
}
|
|
time.Sleep(time.Second / 10)
|
|
}
|
|
}
|