isolated expires list mutex

This commit is contained in:
Josh Baker 2017-09-30 11:06:10 -07:00
parent a99613fe23
commit 1780badf1b
8 changed files with 77 additions and 63 deletions

View File

@ -31,7 +31,7 @@ func (err errAOFHook) Error() string {
var errInvalidAOF = errors.New("invalid aof file") var errInvalidAOF = errors.New("invalid aof file")
func (c *Controller) loadAOF() error { func (c *Controller) loadAOF() error {
fi, err := c.f.Stat() fi, err := c.aof.Stat()
if err != nil { if err != nil {
return err return err
} }
@ -54,7 +54,7 @@ func (c *Controller) loadAOF() error {
count, float64(d)/float64(time.Second), ps, byteSpeed) count, float64(d)/float64(time.Second), ps, byteSpeed)
}() }()
var msg server.Message var msg server.Message
rd := bufio.NewReader(c.f) rd := bufio.NewReader(c.aof)
for { for {
var nn int var nn int
ch, err := rd.ReadByte() ch, err := rd.ReadByte()
@ -194,7 +194,7 @@ func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error {
if err != nil { if err != nil {
return err return err
} }
n, err := c.f.Write(data) n, err := c.aof.Write(data)
if err != nil { if err != nil {
return err return err
} }
@ -342,7 +342,7 @@ func (c *Controller) cmdAOF(msg *server.Message) (res string, err error) {
if err != nil || pos < 0 { if err != nil || pos < 0 {
return "", errInvalidArgument(spos) return "", errInvalidArgument(spos)
} }
f, err := os.Open(c.f.Name()) f, err := os.Open(c.aof.Name())
if err != nil { if err != nil {
return "", err return "", err
} }
@ -375,7 +375,7 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.AnyReaderWrite
} }
c.mu.RLock() c.mu.RLock()
f, err := os.Open(c.f.Name()) f, err := os.Open(c.aof.Name())
c.mu.RUnlock() c.mu.RUnlock()
if err != nil { if err != nil {
return err return err

View File

@ -263,7 +263,7 @@ func (c *Controller) aofshrink() {
// anything below this point is unrecoverable. just log and exit process // anything below this point is unrecoverable. just log and exit process
// back up the live aof, just in case of fatal error // back up the live aof, just in case of fatal error
if err := c.f.Close(); err != nil { if err := c.aof.Close(); err != nil {
log.Fatalf("shink live aof close fatal operation: %v", err) log.Fatalf("shink live aof close fatal operation: %v", err)
} }
if err := os.Rename(path.Join(c.dir, "appendonly.aof"), path.Join(c.dir, "appendonly.bak")); err != nil { if err := os.Rename(path.Join(c.dir, "appendonly.aof"), path.Join(c.dir, "appendonly.bak")); err != nil {
@ -272,12 +272,12 @@ func (c *Controller) aofshrink() {
if err := os.Rename(path.Join(c.dir, "shrink"), path.Join(c.dir, "appendonly.aof")); err != nil { if err := os.Rename(path.Join(c.dir, "shrink"), path.Join(c.dir, "appendonly.aof")); err != nil {
log.Fatalf("shink rename fatal operation: %v", err) log.Fatalf("shink rename fatal operation: %v", err)
} }
c.f, err = os.OpenFile(path.Join(c.dir, "appendonly.aof"), os.O_CREATE|os.O_RDWR, 0600) c.aof, err = os.OpenFile(path.Join(c.dir, "appendonly.aof"), os.O_CREATE|os.O_RDWR, 0600)
if err != nil { if err != nil {
log.Fatalf("shink openfile fatal operation: %v", err) log.Fatalf("shink openfile fatal operation: %v", err)
} }
var n int64 var n int64
n, err = c.f.Seek(0, 2) n, err = c.aof.Seek(0, 2)
if err != nil { if err != nil {
log.Fatalf("shink seek end fatal operation: %v", err) log.Fatalf("shink seek end fatal operation: %v", err)
} }

View File

@ -19,7 +19,7 @@ func (c *Controller) checksum(pos, size int64) (sum string, err error) {
return "", io.EOF return "", io.EOF
} }
var f *os.File var f *os.File
f, err = os.Open(c.f.Name()) f, err = os.Open(c.aof.Name())
if err != nil { if err != nil {
return return
} }
@ -186,10 +186,10 @@ func (c *Controller) followCheckSome(addr string, followc int) (pos int64, err e
} }
} }
fullpos := pos fullpos := pos
fname := c.f.Name() fname := c.aof.Name()
if pos == 0 { if pos == 0 {
c.f.Close() c.aof.Close()
c.f, err = os.Create(fname) c.aof, err = os.Create(fname)
if err != nil { if err != nil {
log.Fatalf("could not recreate aof, possible data loss. %s", err.Error()) log.Fatalf("could not recreate aof, possible data loss. %s", err.Error())
return 0, err return 0, err
@ -199,7 +199,7 @@ func (c *Controller) followCheckSome(addr string, followc int) (pos int64, err e
// we want to truncate at a command location // we want to truncate at a command location
// search for nearest command // search for nearest command
pos, err = getEndOfLastValuePositionInFile(c.f.Name(), fullpos) pos, err = getEndOfLastValuePositionInFile(c.aof.Name(), fullpos)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -211,12 +211,12 @@ func (c *Controller) followCheckSome(addr string, followc int) (pos int64, err e
} }
log.Warnf("truncating aof to %d", pos) log.Warnf("truncating aof to %d", pos)
// any errror below are fatal. // any errror below are fatal.
c.f.Close() c.aof.Close()
if err := os.Truncate(fname, pos); err != nil { if err := os.Truncate(fname, pos); err != nil {
log.Fatalf("could not truncate aof, possible data loss. %s", err.Error()) log.Fatalf("could not truncate aof, possible data loss. %s", err.Error())
return 0, err return 0, err
} }
c.f, err = os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0600) c.aof, err = os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0600)
if err != nil { if err != nil {
log.Fatalf("could not create aof, possible data loss. %s", err.Error()) log.Fatalf("could not create aof, possible data loss. %s", err.Error())
return 0, err return 0, err

View File

@ -82,7 +82,7 @@ func (c *Controller) cmdClient(msg *server.Message, conn *server.Conn) (string,
} }
var list []*clientConn var list []*clientConn
c.connsmu.RLock() c.connsmu.RLock()
for _, cc := range c.conns2 { for _, cc := range c.conns {
list = append(list, cc) list = append(list, cc)
} }
c.connsmu.RUnlock() c.connsmu.RUnlock()
@ -115,7 +115,7 @@ func (c *Controller) cmdClient(msg *server.Message, conn *server.Conn) (string,
} }
name := "" name := ""
c.connsmu.RLock() c.connsmu.RLock()
if cc, ok := c.conns2[conn]; ok { if cc, ok := c.conns[conn]; ok {
name = cc.name.get() name = cc.name.get()
} }
c.connsmu.RUnlock() c.connsmu.RUnlock()
@ -141,7 +141,7 @@ func (c *Controller) cmdClient(msg *server.Message, conn *server.Conn) (string,
} }
} }
c.connsmu.RLock() c.connsmu.RLock()
if cc, ok := c.conns2[conn]; ok { if cc, ok := c.conns[conn]; ok {
cc.name.set(name) cc.name.set(name)
} }
c.connsmu.RUnlock() c.connsmu.RUnlock()
@ -187,7 +187,7 @@ func (c *Controller) cmdClient(msg *server.Message, conn *server.Conn) (string,
} }
var cclose *clientConn var cclose *clientConn
c.connsmu.RLock() c.connsmu.RLock()
for _, cc := range c.conns2 { for _, cc := range c.conns {
if useID && fmt.Sprintf("%d", cc.id) == id { if useID && fmt.Sprintf("%d", cc.id) == id {
cclose = cc cclose = cc
break break

View File

@ -64,6 +64,7 @@ type Controller struct {
host string host string
port int port int
http bool http bool
dir string
started time.Time started time.Time
config *Config config *Config
epc *endpoint.EndpointManager epc *endpoint.EndpointManager
@ -81,15 +82,19 @@ type Controller struct {
outOfMemory abool outOfMemory abool
connsmu sync.RWMutex connsmu sync.RWMutex
conns2 map[*server.Conn]*clientConn conns map[*server.Conn]*clientConn
exlistmu sync.RWMutex
exlist []exitem
mu sync.RWMutex
aof *os.File // active aof file
aofsz int // active size of the aof file
qdb *buntdb.DB // hook queue log
qidx uint64 // hook queue log last idx
cols *btree.BTree // data collections
expires map[string]map[string]time.Time // synced with cols
mu sync.RWMutex
f *os.File
qdb *buntdb.DB // hook queue log
qidx uint64 // hook queue log last idx
cols *btree.BTree
aofsz int
dir string
follows map[*bytes.Buffer]bool follows map[*bytes.Buffer]bool
fcond *sync.Cond fcond *sync.Cond
lstack []*commandDetailsT lstack []*commandDetailsT
@ -102,8 +107,6 @@ type Controller struct {
hooks map[string]*Hook // hook name hooks map[string]*Hook // hook name
hookcols map[string]map[string]*Hook // col key hookcols map[string]map[string]*Hook // col key
aofconnM map[net.Conn]bool aofconnM map[net.Conn]bool
expires map[string]map[string]time.Time
exlist []exitem
} }
// ListenAndServe starts a new tile38 server // ListenAndServe starts a new tile38 server
@ -126,7 +129,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
aofconnM: make(map[net.Conn]bool), aofconnM: make(map[net.Conn]bool),
expires: make(map[string]map[string]time.Time), expires: make(map[string]map[string]time.Time),
started: time.Now(), started: time.Now(),
conns2: make(map[*server.Conn]*clientConn), conns: make(map[*server.Conn]*clientConn),
epc: endpoint.NewEndpointManager(), epc: endpoint.NewEndpointManager(),
http: http, http: http,
} }
@ -161,6 +164,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
if err != nil { if err != nil {
return err return err
} }
c.qdb = qdb c.qdb = qdb
c.qidx = qidx c.qidx = qidx
if err := c.migrateAOF(); err != nil { if err := c.migrateAOF(); err != nil {
@ -170,7 +174,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
if err != nil { if err != nil {
return err return err
} }
c.f = f c.aof = f
if err := c.loadAOF(); err != nil { if err := c.loadAOF(); err != nil {
return err return err
} }
@ -192,7 +196,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
}() }()
handler := func(conn *server.Conn, msg *server.Message, rd *server.AnyReaderWriter, w io.Writer, websocket bool) error { handler := func(conn *server.Conn, msg *server.Message, rd *server.AnyReaderWriter, w io.Writer, websocket bool) error {
c.connsmu.RLock() c.connsmu.RLock()
if cc, ok := c.conns2[conn]; ok { if cc, ok := c.conns[conn]; ok {
cc.last.set(time.Now()) cc.last.set(time.Now())
} }
c.connsmu.RUnlock() c.connsmu.RUnlock()
@ -236,7 +240,7 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
cc.conn = conn cc.conn = conn
c.connsmu.Lock() c.connsmu.Lock()
c.conns2[conn] = cc c.conns[conn] = cc
c.connsmu.Unlock() c.connsmu.Unlock()
c.statsTotalConns.add(1) c.statsTotalConns.add(1)
@ -244,9 +248,10 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
closed := func(conn *server.Conn) { closed := func(conn *server.Conn) {
c.connsmu.Lock() c.connsmu.Lock()
delete(c.conns2, conn) delete(c.conns, conn)
c.connsmu.Unlock() c.connsmu.Unlock()
} }
return server.ListenAndServe(host, port, protected, handler, opened, closed, ln, http) return server.ListenAndServe(host, port, protected, handler, opened, closed, ln, http)
} }

View File

@ -465,7 +465,10 @@ func (c *Controller) cmdFlushDB(msg *server.Message) (res string, d commandDetai
return return
} }
c.cols = btree.New(16, 0) c.cols = btree.New(16, 0)
c.clearAllExpires() c.exlistmu.Lock()
c.exlist = nil
c.exlistmu.Unlock()
c.expires = make(map[string]map[string]time.Time)
c.hooks = make(map[string]*Hook) c.hooks = make(map[string]*Hook)
c.hookcols = make(map[string]map[string]*Hook) c.hookcols = make(map[string]map[string]*Hook)
d.command = "flushdb" d.command = "flushdb"

View File

@ -32,9 +32,16 @@ func (a *exitem) Less(v btree.Item, ctx interface{}) bool {
return a.id < b.id return a.id < b.id
} }
// clearAllExpires removes all items that are marked at expires. // fillExpiresList occurs once at startup
func (c *Controller) clearAllExpires() { func (c *Controller) fillExpiresList() {
c.expires = make(map[string]map[string]time.Time) 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. // clearIDExpires clears a single item from the expires list.
@ -67,9 +74,9 @@ func (c *Controller) expireAt(key, id string, at time.Time) {
c.expires[key] = m c.expires[key] = m
} }
m[id] = at m[id] = at
if c.exlist != nil { c.exlistmu.Lock()
c.exlist = append(c.exlist, exitem{key, id, at}) c.exlist = append(c.exlist, exitem{key, id, at})
} c.exlistmu.Unlock()
} }
// getExpires returns the when an item expires. // getExpires returns the when an item expires.
@ -94,33 +101,35 @@ func (c *Controller) hasExpired(key, id string) bool {
return time.Now().After(at) return time.Now().After(at)
} }
func (c *Controller) fillExpiresList() {
c.exlist = make([]exitem, 0)
for key, m := range c.expires {
for id, at := range m {
c.exlist = append(c.exlist, exitem{key, id, at})
}
}
}
// backgroundExpiring watches for when items that have expired must be purged // backgroundExpiring watches for when items that have expired must be purged
// from the database. It's executes 10 times a seconds. // from the database. It's executes 10 times a seconds.
func (c *Controller) backgroundExpiring() { func (c *Controller) backgroundExpiring() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
var purgelist []exitem
for { for {
c.mu.Lock()
if c.stopBackgroundExpiring.on() { if c.stopBackgroundExpiring.on() {
c.mu.Unlock()
return return
} }
now := time.Now() now := time.Now()
var purged int purgelist = purgelist[:0]
c.exlistmu.Lock()
for i := 0; i < 20 && len(c.exlist) > 0; i++ { for i := 0; i < 20 && len(c.exlist) > 0; i++ {
ix := rand.Int() % len(c.exlist) ix := rand.Int() % len(c.exlist)
if now.After(c.exlist[ix].at) { if now.After(c.exlist[ix].at) {
if c.hasExpired(c.exlist[ix].key, c.exlist[ix].id) { // 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 := &server.Message{}
msg.Values = resp.MultiBulkValue("del", c.exlist[ix].key, c.exlist[ix].id).Array() msg.Values = resp.MultiBulkValue("del", item.key, item.id).Array()
msg.Command = "del" msg.Command = "del"
_, d, err := c.cmdDel(msg) _, d, err := c.cmdDel(msg)
if err != nil { if err != nil {
@ -133,15 +142,12 @@ func (c *Controller) backgroundExpiring() {
log.Fatal(err) log.Fatal(err)
continue continue
} }
purged++
} }
c.exlist[ix] = c.exlist[len(c.exlist)-1]
c.exlist = c.exlist[:len(c.exlist)-1]
} }
} c.mu.Unlock()
c.mu.Unlock() if len(purgelist) > 5 {
if purged > 5 { continue
continue }
} }
time.Sleep(time.Second / 10) time.Sleep(time.Second / 10)
} }

View File

@ -146,7 +146,7 @@ func (c *Controller) writeInfoServer(w *bytes.Buffer) {
} }
func (c *Controller) writeInfoClients(w *bytes.Buffer) { func (c *Controller) writeInfoClients(w *bytes.Buffer) {
c.connsmu.RLock() c.connsmu.RLock()
fmt.Fprintf(w, "connected_clients:%d\r\n", len(c.conns2)) // Number of client connections (excluding connections from slaves) fmt.Fprintf(w, "connected_clients:%d\r\n", len(c.conns)) // Number of client connections (excluding connections from slaves)
c.connsmu.RUnlock() c.connsmu.RUnlock()
} }
func (c *Controller) writeInfoMemory(w *bytes.Buffer) { func (c *Controller) writeInfoMemory(w *bytes.Buffer) {