- 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.
153 lines
3.3 KiB
Go
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
|
|
}
|