yao/neo/message/queue.go
Max 93be3acb97 refactor: Improve asynchronous message processing in WriteMessageAsync
- Introduce a done channel to track message processing completion.
- Increase timeout for message processing to 5 seconds for better reliability.
- Adjust queue timeout to 1 second to enhance responsiveness when the queue is full.
2025-04-27 12:19:54 +08:00

153 lines
3.3 KiB
Go

package message
import (
"sync"
"time"
"github.com/fatih/color"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/kun/log"
)
// AsyncMessageQueue represents a queue for handling message writes
type AsyncMessageQueue struct {
queue chan *AsyncTask
workers int
wg sync.WaitGroup
shutdown chan struct{}
}
// AsyncTask represents a task to write a message
type AsyncTask struct {
message *Message
writer gin.ResponseWriter
done chan bool
}
var (
defaultQueue *AsyncMessageQueue
queueOnce sync.Once
)
// GetQueue returns the default message queue instance
func GetQueue() *AsyncMessageQueue {
queueOnce.Do(func() {
defaultQueue = NewAsyncQueue(10) // Initialize with 10 workers
defaultQueue.Start()
})
return defaultQueue
}
// NewAsyncQueue creates a new message queue with the specified number of workers
func NewAsyncQueue(workers int) *AsyncMessageQueue {
return &AsyncMessageQueue{
queue: make(chan *AsyncTask, 1000), // Buffer size of 1000
workers: workers,
shutdown: make(chan struct{}),
}
}
// Start starts the message queue workers
func (mq *AsyncMessageQueue) Start() {
for i := 0; i < mq.workers; i++ {
mq.wg.Add(1)
go mq.worker()
}
}
// Stop stops the message queue workers
func (mq *AsyncMessageQueue) Stop() {
close(mq.shutdown)
mq.wg.Wait()
}
// worker processes messages from the queue
func (mq *AsyncMessageQueue) worker() {
defer mq.wg.Done()
for {
select {
case task := <-mq.queue:
if task == nil {
continue
}
success := writeMessageToResponse(task.message, task.writer)
if task.done != nil {
task.done <- success
}
case <-mq.shutdown:
return
}
}
}
// WriteMessageAsync writes the message to response writer using the message queue
func WriteMessageAsync(m *Message, w gin.ResponseWriter) bool {
done := make(chan bool, 1)
task := &AsyncTask{
message: m,
writer: w,
done: done,
}
// Try to send the task to the queue with a timeout
select {
case GetQueue().queue <- task:
// Wait for the message to be processed with a longer timeout
select {
case success := <-done:
return success
case <-time.After(5 * time.Second): // Increased timeout to 5 seconds
log.Error("Message processing timeout")
return false
}
case <-time.After(1 * time.Second): // Increased queue timeout to 1 second
log.Error("Queue is full, message dropped")
return false
}
}
// writeMessageToResponse writes the message directly to the response writer
func writeMessageToResponse(m *Message, w gin.ResponseWriter) bool {
// Sync write to response writer
locker.Lock()
defer locker.Unlock()
defer func() {
if r := recover(); r != nil {
// Ignore if done is true
if m.IsDone {
return
}
message := "Write Response Exception: (if client close the connection, it's normal) \n %s\n\n"
color.Red(message, r)
// Print the message
raw, _ := jsoniter.MarshalToString(m)
color.White("Message:\n %s", raw)
}
}()
// Ignore silent messages
if m.Silent {
return true
}
data, err := jsoniter.Marshal(m)
if err != nil {
log.Error("%s", err.Error())
return false
}
data = append([]byte("data: "), data...)
data = append(data, []byte("\n\n")...)
if _, err := w.Write(data); err != nil {
color.Red("Write JSON Message Error: %s", err.Error())
return false
}
w.Flush()
return true
}