mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
- Run goimports on all pkg files - Ensure consistent import grouping throughout codebase
163 lines
4.2 KiB
Go
163 lines
4.2 KiB
Go
package messaging
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"github.com/luxfi/mpc/pkg/logger"
|
|
)
|
|
|
|
var (
|
|
ErrPermament = errors.New("Permanent messaging error")
|
|
)
|
|
|
|
type MessageQueue interface {
|
|
Enqueue(topic string, message []byte, options *EnqueueOptions) error
|
|
Dequeue(topic string, handler func(message []byte) error) error
|
|
Close()
|
|
}
|
|
|
|
type EnqueueOptions struct {
|
|
IdempotententKey string
|
|
}
|
|
|
|
type msgQueue struct {
|
|
consumerName string
|
|
js jetstream.JetStream
|
|
consumer jetstream.Consumer
|
|
consumerContext jetstream.ConsumeContext
|
|
}
|
|
|
|
type NATsMessageQueueManager struct {
|
|
queueName string
|
|
js jetstream.JetStream
|
|
}
|
|
|
|
func NewNATsMessageQueueManager(queueName string, subjectWildCards []string, nc *nats.Conn) *NATsMessageQueueManager {
|
|
js, err := jetstream.New(nc)
|
|
if err != nil {
|
|
logger.Fatal("Error creating JetStream context: ", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
stream, err := js.Stream(ctx, queueName)
|
|
if err != nil {
|
|
logger.Warn("Stream not found, creating new stream", "stream", queueName)
|
|
}
|
|
if stream != nil {
|
|
info, _ := stream.Info(ctx)
|
|
logger.Debug("Stream found", "info", info)
|
|
|
|
}
|
|
|
|
_, err = js.CreateOrUpdateStream(context.Background(), jetstream.StreamConfig{
|
|
Name: queueName,
|
|
Description: "Stream for " + queueName,
|
|
Subjects: subjectWildCards,
|
|
MaxBytes: 10_485_760, // Light Production (Low Traffic) (10 MB)
|
|
Storage: jetstream.FileStorage,
|
|
Retention: jetstream.WorkQueuePolicy,
|
|
})
|
|
if err != nil {
|
|
logger.Fatal("Error creating JetStream stream: ", err)
|
|
}
|
|
logger.Info("Creating apex NATs Jetstream context successfully!", "streamName", queueName, "subjects", subjectWildCards)
|
|
|
|
return &NATsMessageQueueManager{
|
|
queueName: queueName,
|
|
js: js,
|
|
}
|
|
}
|
|
|
|
func (m *NATsMessageQueueManager) NewMessageQueue(consumerName string) MessageQueue {
|
|
mq := &msgQueue{
|
|
consumerName: consumerName,
|
|
js: m.js,
|
|
}
|
|
consumerWildCard := fmt.Sprintf("%s.%s.*", m.queueName, consumerName)
|
|
cfg := jetstream.ConsumerConfig{
|
|
Name: consumerName,
|
|
Durable: consumerName,
|
|
MaxAckPending: 1000,
|
|
// If a message isn't acked within AckWait, it will be redelivered up to MaxDelive
|
|
AckWait: 30 * time.Second,
|
|
AckPolicy: jetstream.AckExplicitPolicy,
|
|
FilterSubjects: []string{
|
|
consumerWildCard,
|
|
},
|
|
MaxDeliver: 3,
|
|
}
|
|
logger.Info("Creating consumer for subject", "consumerName", consumerName, "queueName", m.queueName, "filterSubject", consumerWildCard, "config", cfg)
|
|
consumer, err := m.js.CreateOrUpdateConsumer(context.Background(), m.queueName, cfg)
|
|
if err != nil {
|
|
logger.Fatal("Error creating JetStream consumer: ", err)
|
|
}
|
|
|
|
mq.consumer = consumer
|
|
return mq
|
|
}
|
|
|
|
func (mq *msgQueue) Enqueue(topic string, message []byte, options *EnqueueOptions) error {
|
|
header := nats.Header{}
|
|
if options != nil {
|
|
header.Add("Nats-Msg-Id", options.IdempotententKey)
|
|
}
|
|
|
|
logger.Info("Publishing message", "topic", topic, "consumerName", mq.consumerName)
|
|
_, err := mq.js.PublishMsg(context.Background(), &nats.Msg{
|
|
Subject: topic,
|
|
Data: message,
|
|
Header: header,
|
|
})
|
|
|
|
if err != nil {
|
|
logger.Error("Failed to publish message to JetStream", err, "topic", topic, "consumerName", mq.consumerName)
|
|
return fmt.Errorf("error enqueueing message: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mq *msgQueue) Dequeue(topic string, handler func(message []byte) error) error {
|
|
c, err := mq.consumer.Consume(func(msg jetstream.Msg) {
|
|
meta, _ := msg.Metadata()
|
|
logger.Debug("Received message", "meta", meta)
|
|
err := handler(msg.Data())
|
|
if err != nil {
|
|
if errors.Is(err, ErrPermament) {
|
|
logger.Info("Permanent error on message", "meta", meta)
|
|
if err := msg.Term(); err != nil {
|
|
logger.Error("Failed to terminate message", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Error("Error handling message: ", err)
|
|
if err := msg.Nak(); err != nil {
|
|
logger.Error("Failed to nak message", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Debug("Message Acknowledged", "meta", meta)
|
|
err = msg.Ack()
|
|
if err != nil {
|
|
logger.Error("Error acknowledging message: ", err)
|
|
}
|
|
})
|
|
mq.consumerContext = c
|
|
return err
|
|
}
|
|
|
|
func (mq *msgQueue) Close() {
|
|
// only close consumer if it was created - dequeue
|
|
if mq.consumerContext != nil {
|
|
mq.consumerContext.Stop()
|
|
}
|
|
}
|