open-nomad/nomad/stream/ndjson.go

87 lines
1.9 KiB
Go
Raw Normal View History

package stream
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/hashicorp/nomad/nomad/structs"
)
var (
// JsonHeartbeat is an empty JSON object to send as a heartbeat
// Avoids creating many heartbeat instances
JsonHeartbeat = &structs.EventJson{Data: []byte("{}")}
)
// JsonStream is used to send new line delimited JSON and heartbeats
// to a destination (out channel)
type JsonStream struct {
// ctx is a passed in context used to notify the json stream
// when it should terminate
ctx context.Context
outCh chan *structs.EventJson
// heartbeat is the interval to send heartbeat messages to keep a connection
// open.
heartbeatTick *time.Ticker
}
// NewJsonStream creates a new json stream that will output Json structs
// to the passed output channel. The constructor starts a goroutine
// to begin heartbeating on its set interval.
func NewJsonStream(ctx context.Context, heartbeat time.Duration) *JsonStream {
s := &JsonStream{
ctx: ctx,
outCh: make(chan *structs.EventJson, 10),
heartbeatTick: time.NewTicker(heartbeat),
}
go s.heartbeat()
return s
}
func (n *JsonStream) OutCh() chan *structs.EventJson {
return n.outCh
}
func (n *JsonStream) heartbeat() {
for {
select {
case <-n.ctx.Done():
return
case <-n.heartbeatTick.C:
// Send a heartbeat frame
select {
case n.outCh <- JsonHeartbeat:
case <-n.ctx.Done():
return
}
}
}
}
// Send encodes an object into Newline delimited json. An error is returned
// if json encoding fails or if the stream is no longer running.
func (n *JsonStream) Send(v interface{}) error {
if n.ctx.Err() != nil {
return n.ctx.Err()
}
buf, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("error marshaling json for stream: %w", err)
}
select {
case <-n.ctx.Done():
return fmt.Errorf("error stream is no longer running: %w", err)
case n.outCh <- &structs.EventJson{Data: buf}:
}
return nil
}