9 "git.curoverse.com/arvados.git/sdk/go/arvados"
10 "git.curoverse.com/arvados.git/sdk/go/stats"
15 PingTimeout time.Duration
19 lastDelay map[chan interface{}]stats.Duration
23 type handlerStats struct {
24 QueueDelayNs time.Duration
25 WriteDelayNs time.Duration
30 func (h *handler) Handle(ws wsConn, eventSource eventSource, newSession func(wsConn, chan<- interface{}) (session, error)) (hStats handlerStats) {
31 h.setupOnce.Do(h.setup)
33 ctx, cancel := context.WithCancel(ws.Request().Context())
37 incoming := eventSource.NewSink()
40 queue := make(chan interface{}, h.QueueSize)
42 h.lastDelay[queue] = 0
46 delete(h.lastDelay, queue)
50 sess, err := newSession(ws, queue)
52 log.WithError(err).Error("newSession failed")
56 // Receive websocket frames from the client and pass them to
59 buf := make([]byte, 2<<20)
66 ws.SetReadDeadline(time.Now().Add(24 * 365 * time.Hour))
67 n, err := ws.Read(buf)
69 log.WithField("frame", string(buf[:n])).Debug("received frame")
70 if err == nil && n == cap(buf) {
75 log.WithError(err).Info("read error")
80 err = sess.Receive(buf)
82 log.WithError(err).Error("sess.Receive() failed")
89 // Take items from the outgoing queue, serialize them using
90 // sess.EventMessage() as needed, and send them to the client
91 // as websocket frames.
99 case data, ok = <-queue:
109 switch data := data.(type) {
114 log = log.WithField("serial", e.Serial)
115 buf, err = sess.EventMessage(e)
117 log.WithError(err).Error("EventMessage failed")
120 } else if len(buf) == 0 {
125 log.WithField("data", data).Error("bad object in client queue")
129 log.WithField("frame", string(buf)).Debug("send event")
130 ws.SetWriteDeadline(time.Now().Add(h.PingTimeout))
132 _, err = ws.Write(buf)
134 log.WithError(err).Error("write failed")
141 hStats.QueueDelayNs += t0.Sub(e.Ready)
143 h.lastDelay[queue] = stats.Duration(time.Since(e.Ready))
146 hStats.WriteDelayNs += time.Since(t0)
147 hStats.EventBytes += uint64(len(buf))
152 // Filter incoming events against the current subscription
153 // list, and forward matching events to the outgoing message
154 // queue. Close the queue and return when the request context
155 // is done/cancelled or the incoming event stream ends. Shut
156 // down the handler if the outgoing queue fills up.
158 ticker := time.NewTicker(h.PingTimeout)
166 // If the outgoing queue is empty,
167 // send an empty message. This can
168 // help detect a disconnected network
169 // socket, and prevent an idle socket
170 // from being closed.
173 case queue <- []byte(`{}`):
178 case e, ok := <-incoming.Channel():
189 log.WithError(errQueueFull).Error("terminate")
201 func (h *handler) DebugStatus() interface{} {
210 QueueDelayMin stats.Duration
211 QueueDelayMax stats.Duration
213 for q, lastDelay := range h.lastDelay {
216 s.QueueTotal += uint64(n)
220 if s.QueueMin > n || s.QueueCount == 1 {
223 if (s.QueueDelayMin > lastDelay || s.QueueDelayMin == 0) && lastDelay > 0 {
224 s.QueueDelayMin = lastDelay
226 if s.QueueDelayMax < lastDelay {
227 s.QueueDelayMax = lastDelay
233 func (h *handler) setup() {
234 h.lastDelay = make(map[chan interface{}]stats.Duration)