ZeroMQ handler in the background with graceful shutdown

This commit is contained in:
Martin Boehm 2018-01-22 16:46:54 +01:00
parent bc5b99003e
commit 4427af769b
2 changed files with 99 additions and 33 deletions

View File

@ -2,38 +2,89 @@ package bitcoin
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"log" "log"
zmq "github.com/pebbe/zmq4" zmq "github.com/pebbe/zmq4"
) )
func ZeroMQ(binding string) { // MQ is message queue listener handle
type MQ struct {
context *zmq.Context
socket *zmq.Socket
isRunning bool
finished chan bool
}
// MQMessage contains data received from Bitcoind message queue
type MQMessage struct {
Topic string
Sequence uint32
Body []byte
}
// New creates new Bitcoind ZeroMQ listener
// callback function receives messages
func New(binding string, callback func(*MQMessage)) (*MQ, error) {
context, err := zmq.NewContext() context, err := zmq.NewContext()
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
socket, err := context.NewSocket(zmq.SUB) socket, err := context.NewSocket(zmq.SUB)
if err != nil { if err != nil {
log.Fatal(err) return nil, err
} }
socket.SetSubscribe("hashblock") socket.SetSubscribe("hashblock")
socket.SetSubscribe("hashtx") socket.SetSubscribe("hashtx")
socket.SetSubscribe("rawblock") socket.SetSubscribe("rawblock")
socket.SetSubscribe("rawtx") socket.SetSubscribe("rawtx")
socket.Connect(binding) socket.Connect(binding)
defer socket.Close() log.Printf("MQ listening to %s", binding)
for i := 0; i < 101; i++ { mq := &MQ{context, socket, true, make(chan bool)}
msg, err := socket.RecvMessageBytes(0) go mq.run(callback)
if err != nil { return mq, nil
log.Fatal(err) }
}
topic := string(msg[0]) func (mq *MQ) run(callback func(*MQMessage)) {
body := hex.EncodeToString(msg[1]) mq.isRunning = true
sequence := uint32(0) for {
if len(msg[len(msg)-1]) == 4 { msg, err := mq.socket.RecvMessageBytes(0)
sequence = binary.LittleEndian.Uint32(msg[len(msg)-1]) if err != nil {
} if zmq.AsErrno(err) == zmq.Errno(zmq.ETERM) {
log.Printf("%s-%d (%v) %s", topic, sequence, msg[len(msg)-1], body) close(mq.finished)
} log.Print("MQ loop terminated")
break
}
log.Printf("MQ RecvMessageBytes error %v", err)
}
if msg != nil && len(msg) >= 3 {
sequence := uint32(0)
if len(msg[len(msg)-1]) == 4 {
sequence = binary.LittleEndian.Uint32(msg[len(msg)-1])
}
m := &MQMessage{
Topic: string(msg[0]),
Sequence: sequence,
Body: msg[1],
}
callback(m)
}
}
mq.isRunning = false
}
// Shutdown stops listening to the ZeroMQ and closes the connection
func (mq *MQ) Shutdown() error {
log.Printf("MQ server shutdown")
if mq.isRunning {
// if errors in socket.Close or context.Term, let it close ungracefully
if err := mq.socket.Close(); err != nil {
return err
}
if err := mq.context.Term(); err != nil {
return err
}
_, _ = <-mq.finished
log.Printf("MQ server shutdown finished")
}
return nil
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"encoding/hex"
"flag" "flag"
"log" "log"
"os" "os"
@ -62,9 +63,8 @@ var (
func main() { func main() {
flag.Parse() flag.Parse()
if *zeroMQBinding != "nil" { if *prof {
bitcoin.ZeroMQ(*zeroMQBinding) defer profile.Start().Stop()
return
} }
if *repair { if *repair {
@ -74,10 +74,6 @@ func main() {
return return
} }
if *prof {
defer profile.Start().Stop()
}
rpc := bitcoin.NewBitcoinRPC( rpc := bitcoin.NewBitcoinRPC(
*rpcURL, *rpcURL,
*rpcUser, *rpcUser,
@ -101,16 +97,24 @@ func main() {
if *httpServerBinding != "nil" { if *httpServerBinding != "nil" {
httpServer, err = server.New(*httpServerBinding, db) httpServer, err = server.New(*httpServerBinding, db)
if err != nil { if err != nil {
log.Fatalf("https: %s", err) log.Fatalf("https: %v", err)
} }
go func() { go func() {
err = httpServer.Run() err = httpServer.Run()
if err != nil { if err != nil {
log.Fatalf("https: %s", err) log.Fatalf("https: %v", err)
} }
}() }()
} }
var mq *bitcoin.MQ
if *zeroMQBinding != "nil" {
mq, err = bitcoin.New(*zeroMQBinding, mqHandler)
if err != nil {
log.Fatalf("mq: %v", err)
}
}
if *resync { if *resync {
if err := resyncIndex(rpc, db); err != nil { if err := resyncIndex(rpc, db); err != nil {
log.Fatal(err) log.Fatal(err)
@ -144,27 +148,38 @@ func main() {
} }
if httpServer != nil { if httpServer != nil {
waitForSignalAndShutdown(httpServer, 5*time.Second) waitForSignalAndShutdown(httpServer, mq, 5*time.Second)
} }
} }
func waitForSignalAndShutdown(s *server.HttpServer, timeout time.Duration) { func mqHandler(m *bitcoin.MQMessage) {
stop := make(chan os.Signal, 1) body := hex.EncodeToString(m.Body)
log.Printf("MQ: %s-%d %s", m.Topic, m.Sequence, body)
}
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) func waitForSignalAndShutdown(s *server.HttpServer, mq *bitcoin.MQ, timeout time.Duration) {
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
sig := <-stop sig := <-stop
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
log.Printf("Shutdown %v", sig) log.Printf("Shutdown with reason: %v", sig)
if mq != nil {
if err := mq.Shutdown(); err != nil {
log.Printf("MQ.Shutdown error: %v", err)
}
}
if s != nil { if s != nil {
if err := s.Shutdown(ctx); err != nil { if err := s.Shutdown(ctx); err != nil {
log.Printf("Error: %v", err) log.Printf("HttpServer.Shutdown error: %v", err)
} }
} }
} }
func printResult(txid string) error { func printResult(txid string) error {