tile38/controller/live.go

149 lines
2.9 KiB
Go

package controller
import (
"bufio"
"bytes"
"errors"
"io"
"net"
"strings"
"sync"
"github.com/tidwall/tile38/client"
"github.com/tidwall/tile38/controller/log"
)
type liveBuffer struct {
key string
glob string
fence *liveFenceSwitches
details []*commandDetailsT
cond *sync.Cond
}
func (c *Controller) processLives() {
for {
c.lcond.L.Lock()
for len(c.lstack) > 0 {
item := c.lstack[0]
c.lstack = c.lstack[1:]
if len(c.lstack) == 0 {
c.lstack = nil
}
for lb := range c.lives {
lb.cond.L.Lock()
if lb.key != "" && lb.key == item.key {
lb.details = append(lb.details, item)
lb.cond.Broadcast()
}
lb.cond.L.Unlock()
}
}
c.lcond.Wait()
c.lcond.L.Unlock()
}
}
func writeMessage(conn net.Conn, message []byte, websocket bool) error {
if websocket {
return client.WriteWebSocket(conn, message)
}
return client.WriteMessage(conn, message)
}
func (c *Controller) goLive(inerr error, conn net.Conn, rd *bufio.Reader, websocket bool) error {
addr := conn.RemoteAddr().String()
log.Info("live " + addr)
defer func() {
log.Info("not live " + addr)
}()
if s, ok := inerr.(liveAOFSwitches); ok {
return c.liveAOF(s.pos, conn, rd)
}
lb := &liveBuffer{
cond: sync.NewCond(&sync.Mutex{}),
}
var err error
var sw *scanWriter
var wr bytes.Buffer
switch s := inerr.(type) {
default:
return errors.New("invalid switch")
case liveFenceSwitches:
lb.glob = s.glob
lb.key = s.key
lb.fence = &s
c.mu.RLock()
sw, err = c.newScanWriter(&wr, s.key, s.output, s.precision, s.glob, s.limit, s.wheres, s.nofields)
c.mu.RUnlock()
}
// everything below if for live SCAN, NEARBY, WITHIN, INTERSECTS
if err != nil {
return err
}
c.lcond.L.Lock()
c.lives[lb] = true
c.lcond.L.Unlock()
defer func() {
c.lcond.L.Lock()
delete(c.lives, lb)
c.lcond.L.Unlock()
conn.Close()
}()
var mustQuit bool
go func() {
defer func() {
lb.cond.L.Lock()
mustQuit = true
lb.cond.Broadcast()
lb.cond.L.Unlock()
conn.Close()
}()
for {
command, _, _, err := client.ReadMessage(rd, nil)
if err != nil {
if err != io.EOF {
log.Error(err)
}
return
}
switch strings.ToLower(string(command)) {
default:
log.Error("received a live command that was not QUIT")
return
case "quit", "":
return
}
}
}()
if err := writeMessage(conn, []byte(client.LiveJSON), websocket); err != nil {
return nil // nil return is fine here
}
for {
lb.cond.L.Lock()
if mustQuit {
lb.cond.L.Unlock()
return nil
}
for len(lb.details) > 0 {
details := lb.details[0]
lb.details = lb.details[1:]
if len(lb.details) == 0 {
lb.details = nil
}
fence := lb.fence
lb.cond.L.Unlock()
msgs := c.FenceMatch("", sw, fence, details, true)
for _, msg := range msgs {
if err := writeMessage(conn, msg, websocket); err != nil {
return nil // nil return is fine here
}
}
lb.cond.L.Lock()
}
lb.cond.Wait()
lb.cond.L.Unlock()
}
}