github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/framework/queue/queue.go (about) 1 package queue 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "os" 8 "os/signal" 9 "sync" 10 "syscall" 11 "time" 12 13 // "github.com/mdaxf/iac/controllers/trans" 14 "github.com/mdaxf/iac-signalr/signalr" 15 "github.com/mdaxf/iac/com" 16 dbconn "github.com/mdaxf/iac/databases" 17 "github.com/mdaxf/iac/documents" 18 "github.com/mdaxf/iac/engine/trancode" 19 "github.com/mdaxf/iac/logger" 20 ) 21 22 type MessageQueue struct { 23 QueueID string 24 QueuName string 25 messages []Message 26 lock sync.Mutex 27 iLog logger.Log 28 DocDBconn *documents.DocDB 29 DB *sql.DB 30 SignalRClient signalr.Client 31 } 32 33 type Message struct { 34 Id string 35 UUID string 36 Retry int 37 Execute int 38 Topic string 39 PayLoad []byte 40 Handler string 41 CreatedOn time.Time 42 ExecutedOn time.Time 43 CompletedOn time.Time 44 } 45 46 type PayLoad struct { 47 Topic string `json:"Topic"` 48 Payload string `json:"Payload"` 49 } 50 51 // NewMessageQueue creates a new instance of MessageQueue with the specified Id and Name. 52 // It initializes the logger and sets the necessary connections. 53 // It also starts the execution of the MessageQueue in a separate goroutine. 54 // Returns a pointer to the created MessageQueue. 55 56 func NewMessageQueue(Id string, Name string) *MessageQueue { 57 58 iLog := logger.Log{ModuleName: logger.Framework, User: "System", ControllerName: "MessageQueue"} 59 /* startTime := time.Now() 60 defer func() { 61 elapsed := time.Since(startTime) 62 iLog.PerformanceWithDuration("framework.queue.NewMessageQueue", elapsed) 63 }() 64 defer func() { 65 if r := recover(); r != nil { 66 iLog.Error(fmt.Sprintf("Error in framework.queue.NewMessageQueue: %s", r)) 67 return 68 } 69 }() */ 70 71 iLog.Debug(fmt.Sprintf(("Create MessageQueue %s %s"), Id, Name)) 72 73 mq := &MessageQueue{ 74 QueueID: Id, 75 QueuName: Name, 76 iLog: iLog, 77 } 78 79 mq.DocDBconn = documents.DocDBCon 80 mq.SignalRClient = com.IACMessageBusClient 81 mq.DB = dbconn.DB 82 go mq.execute() 83 84 return mq 85 } 86 87 // NewMessageQueuebyExternal creates a new instance of MessageQueue with the given parameters. 88 // It takes an Id string, a Name string, a DB *sql.DB, a DocDBconn *documents.DocDB, and a SignalRClient signalr.Client. 89 // It returns a pointer to the created MessageQueue. 90 91 func NewMessageQueuebyExternal(Id string, Name string, DB *sql.DB, DocDBconn *documents.DocDB, SignalRClient signalr.Client) *MessageQueue { 92 93 iLog := logger.Log{ModuleName: logger.Framework, User: "System", ControllerName: "MessageQueue"} 94 /* startTime := time.Now() 95 defer func() { 96 elapsed := time.Since(startTime) 97 iLog.PerformanceWithDuration("framework.queue.NewMessageQueuebyExternal", elapsed) 98 }() 99 defer func() { 100 if r := recover(); r != nil { 101 iLog.Error(fmt.Sprintf("Error in framework.queue.NewMessageQueuebyExternal: %s", r)) 102 return 103 } 104 }() */ 105 iLog.Debug(fmt.Sprintf(("Create MessageQueue %s %s"), Id, Name)) 106 107 mq := &MessageQueue{ 108 QueueID: Id, 109 QueuName: Name, 110 iLog: iLog, 111 DocDBconn: DocDBconn, 112 SignalRClient: SignalRClient, 113 DB: DB, 114 } 115 116 go mq.execute() 117 118 return mq 119 } 120 121 // Push adds a message to the message queue. 122 // It measures the performance duration of the operation and logs any errors that occur. 123 // The message is appended to the queue and can be accessed later. 124 // It takes a Message struct as a parameter. 125 126 func (mq *MessageQueue) Push(message Message) { 127 startTime := time.Now() 128 defer func() { 129 elapsed := time.Since(startTime) 130 mq.iLog.PerformanceWithDuration("framework.queue.Push", elapsed) 131 }() 132 defer func() { 133 if r := recover(); r != nil { 134 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.Push: %s", r)) 135 return 136 } 137 }() 138 mq.lock.Lock() 139 defer mq.lock.Unlock() 140 mq.iLog.Debug(fmt.Sprintf("Push message %v to queue: %s", message, mq.QueueID)) 141 mq.messages = append(mq.messages, message) 142 } 143 144 // Pop removes and returns the first message from the message queue. 145 // If the queue is empty, it returns an empty Message. 146 // It measures the performance duration of the operation and logs any errors that occur. 147 func (mq *MessageQueue) Pop() Message { 148 startTime := time.Now() 149 defer func() { 150 elapsed := time.Since(startTime) 151 mq.iLog.PerformanceWithDuration("framework.queue.Pop", elapsed) 152 }() 153 defer func() { 154 if r := recover(); r != nil { 155 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.Pop: %s", r)) 156 return 157 } 158 }() 159 mq.lock.Lock() 160 defer mq.lock.Unlock() 161 mq.iLog.Debug(fmt.Sprintf("Pop message from queue which queue length is %d", len(mq.messages))) 162 if len(mq.messages) == 0 { 163 return Message{} 164 } 165 mq.iLog.Debug(fmt.Sprintf("Pop message from queue: %v", mq.messages[0])) 166 message := mq.messages[0] 167 mq.messages = mq.messages[1:] 168 return message 169 } 170 171 // Length returns the number of messages in the message queue. 172 func (mq *MessageQueue) Length() int { 173 mq.lock.Lock() 174 defer mq.lock.Unlock() 175 return len(mq.messages) 176 } 177 178 // Clear removes all messages from the message queue. 179 // It acquires a lock to ensure thread safety and sets the messages slice to nil. 180 // Any error that occurs during the clearing process is recovered and logged. 181 // The performance duration of the Clear operation is also logged. 182 183 func (mq *MessageQueue) Clear() { 184 startTime := time.Now() 185 defer func() { 186 elapsed := time.Since(startTime) 187 mq.iLog.PerformanceWithDuration("framework.queue.Clear", elapsed) 188 }() 189 defer func() { 190 if r := recover(); r != nil { 191 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.Clear: %s", r)) 192 return 193 } 194 }() 195 mq.lock.Lock() 196 defer mq.lock.Unlock() 197 mq.messages = nil 198 } 199 200 // WaitAndPop waits for a message to be available in the queue and then removes and returns it. 201 // If the queue is empty, it will return an empty Message struct. 202 // The timeout parameter specifies the maximum duration to wait for a message before returning. 203 // If a timeout of zero is provided, it will wait indefinitely until a message is available. 204 // It measures the performance duration of the operation and logs any errors that occur. 205 // It takes a timeout time.Duration as a parameter and returns a Message struct. 206 func (mq *MessageQueue) WaitAndPop(timeout time.Duration) Message { 207 startTime := time.Now() 208 defer func() { 209 elapsed := time.Since(startTime) 210 mq.iLog.PerformanceWithDuration("framework.queue.WaitAndPop", elapsed) 211 }() 212 defer func() { 213 if r := recover(); r != nil { 214 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.WaitAndPop: %s", r)) 215 return 216 } 217 }() 218 mq.lock.Lock() 219 defer mq.lock.Unlock() 220 mq.iLog.Debug(fmt.Sprintf("WaitAndPop message from queue which queue length is %d", len(mq.messages))) 221 if len(mq.messages) == 0 { 222 mq.iLog.Debug(fmt.Sprintf("WaitAndPop message from queue which queue length is %d", len(mq.messages))) 223 return Message{} 224 } 225 mq.iLog.Debug(fmt.Sprintf("WaitAndPop message from queue: %v", mq.messages[0])) 226 message := mq.messages[0] 227 mq.messages = mq.messages[1:] 228 return message 229 } 230 231 // WaitAndPopWithTimeout waits for a specified duration and pops a message from the message queue. 232 // If the queue is empty, it returns an empty message. 233 // It also logs the performance duration of the function. 234 // If there is an error during the execution, it recovers and logs the error. 235 // This function is thread-safe. 236 // 237 // Parameters: 238 // - timeout: The duration to wait for a message before timing out. 239 // 240 // Returns: 241 // - Message: The popped message from the queue. 242 243 func (mq *MessageQueue) WaitAndPopWithTimeout(timeout time.Duration) Message { 244 startTime := time.Now() 245 defer func() { 246 elapsed := time.Since(startTime) 247 mq.iLog.PerformanceWithDuration("framework.queue.WaitAndPopWithTimeout", elapsed) 248 }() 249 defer func() { 250 if r := recover(); r != nil { 251 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.WaitAndPopWithTimeout: %s", r)) 252 return 253 } 254 }() 255 mq.lock.Lock() 256 defer mq.lock.Unlock() 257 mq.iLog.Debug(fmt.Sprintf("WaitAndPopWithTimeout message from queue which queue length is %d", len(mq.messages))) 258 if len(mq.messages) == 0 { 259 mq.iLog.Debug(fmt.Sprintf("WaitAndPopWithTimeout message from queue which queue length is %d", len(mq.messages))) 260 return Message{} 261 } 262 mq.iLog.Debug(fmt.Sprintf("WaitAndPopWithTimeout message from queue: %v", mq.messages[0])) 263 message := mq.messages[0] 264 mq.messages = mq.messages[1:] 265 return message 266 } 267 268 // Peek returns the first message in the message queue without removing it. 269 // If the queue is empty, it returns an empty Message. 270 // It measures the performance duration of the operation and logs any errors that occur. 271 func (mq *MessageQueue) Peek() Message { 272 startTime := time.Now() 273 defer func() { 274 elapsed := time.Since(startTime) 275 mq.iLog.PerformanceWithDuration("framework.queue.Peek", elapsed) 276 }() 277 defer func() { 278 if r := recover(); r != nil { 279 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.Peek: %s", r)) 280 return 281 } 282 }() 283 mq.lock.Lock() 284 defer mq.lock.Unlock() 285 mq.iLog.Debug(fmt.Sprintf("Peek message from queue which queue length is %d", len(mq.messages))) 286 if len(mq.messages) == 0 { 287 return Message{} 288 } 289 mq.iLog.Debug(fmt.Sprintf("Peek message from queue: %v", mq.messages[0])) 290 message := mq.messages[0] 291 return message 292 } 293 294 // execute is a method of the MessageQueue struct that starts the execution of message processing. 295 // It creates worker goroutines to process messages from the queue until a termination signal is received. 296 // The method also measures the performance of the execution and handles any panics that occur during processing. 297 // It is called by the NewMessageQueue function. 298 299 func (mq *MessageQueue) execute() { 300 startTime := time.Now() 301 defer func() { 302 elapsed := time.Since(startTime) 303 mq.iLog.PerformanceWithDuration("framework.queue.execute", elapsed) 304 }() 305 defer func() { 306 if r := recover(); r != nil { 307 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.execute: %s", r)) 308 return 309 } 310 }() 311 numMessages := 10 312 //maxWorkers := 10 313 314 // Create a wait group to synchronize the workers 315 var wg sync.WaitGroup 316 n := 0 317 go func() { 318 // Start the workers 319 for { 320 321 defer wg.Done() 322 323 if mq.Length() == 0 { 324 time.Sleep(time.Millisecond * 500) 325 continue 326 } 327 n += 1 328 wg.Add(1) 329 workermessageQueue := make(chan Message, numMessages) 330 for i := 0; i < numMessages; i++ { 331 workermessageQueue <- mq.Pop() 332 if mq.Length() == 0 { 333 break 334 } 335 } 336 mq.iLog.Debug(fmt.Sprintf("creating worker %d has %v jobs, %v", n, len(workermessageQueue), workermessageQueue)) 337 338 close(workermessageQueue) 339 mq.iLog.Debug(fmt.Sprintf("complete create worker %d has %d jobs, %v", n, len(workermessageQueue), workermessageQueue)) 340 go mq.worker(n, workermessageQueue, &wg) 341 342 time.Sleep(time.Millisecond * 500) 343 } 344 345 }() 346 347 wg.Wait() 348 mq.waitForTerminationSignal() 349 } 350 351 // waitForTerminationSignal waits for a termination signal and performs cleanup or graceful shutdown logic. 352 // It listens for an interrupt signal or SIGTERM signal, and upon receiving the signal, it prints a shutdown message, 353 // sleeps for 2 seconds, and exits the program with a status code of 0. 354 // It measures the performance duration of the operation and logs any errors that occur. 355 // It is called by the execute method. 356 // It is also called by the NewMessageQueue function. 357 358 func (mq *MessageQueue) waitForTerminationSignal() { 359 /* startTime := time.Now() 360 defer func() { 361 elapsed := time.Since(startTime) 362 mq.iLog.PerformanceWithDuration("framework.queue.waitForTerminationSignal", elapsed) 363 }() 364 defer func() { 365 if r := recover(); r != nil { 366 mq.iLog.Error(fmt.Sprintf("Error in framework.queue.waitForTerminationSignal: %s", r)) 367 return 368 } 369 }() */ 370 c := make(chan os.Signal, 1) 371 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 372 <-c 373 fmt.Println("\nShutting down...") 374 375 time.Sleep(2 * time.Second) // Add any cleanup or graceful shutdown logic here 376 os.Exit(0) 377 } 378 379 func (mq *MessageQueue) processMessage(message Message) error { 380 startTime := time.Now() 381 defer func() { 382 elapsed := time.Since(startTime) 383 mq.iLog.PerformanceWithDuration("framework.queue.processMessage", elapsed) 384 }() 385 386 defer func() { 387 if r := recover(); r != nil { 388 mq.iLog.Error(fmt.Sprintf("framework.queue.processMessage failed to process message: %v", r)) 389 return 390 } 391 }() 392 393 mq.iLog.Debug(fmt.Sprintf("handlemessagefromqueue message from queue: %v", message)) 394 395 if message.Handler != "" && message.Topic != "" { 396 // Unmarshal the JSON data into a Message object 397 message.ExecutedOn = time.Now().UTC() 398 // mq.iLog.Debug(fmt.Sprintf("handlemessagefromqueue message from queue: %v", message)) 399 400 //handler := M.handler 401 //data := M.data 402 //handler(data) 403 var err error 404 message.Execute = message.Execute + 1 405 406 mq.iLog.Debug(fmt.Sprintf("execute the message %s with data %v with handler %v", message.Id, message.PayLoad, message.Handler)) 407 408 data := make(map[string]interface{}) 409 /* 410 jsondata, err := json.Marshal(message.PayLoad) 411 if err != nil { 412 mq.iLog.Error(fmt.Sprintf("Failed to convert json to map: %v", err)) 413 return err 414 } 415 mq.iLog.Debug(fmt.Sprintf("Message payload data %s, %v", jsondata, message.PayLoad)) 416 */ 417 data["Topic"] = message.Topic 418 data["Payload"] = string(message.PayLoad) 419 420 data["ID"] = message.Id 421 data["UUID"] = message.UUID 422 data["CreatedOn"] = message.CreatedOn 423 424 // data = com.ConvertstructToMap(message) 425 mq.iLog.Debug(fmt.Sprintf("Message data %v", data)) 426 427 if mq.DB == nil { 428 mq.DB = dbconn.DB 429 } 430 431 if mq.DB == nil { 432 mq.iLog.Error(fmt.Sprintf("Failed to get database connection")) 433 return err 434 } 435 436 tx, err := mq.DB.BeginTx(context.TODO(), &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}) 437 if err != nil { 438 mq.iLog.Error(fmt.Sprintf("Failed to begin transaction: %v", err)) 439 return err 440 } 441 defer tx.Rollback() 442 mq.iLog.Debug(fmt.Sprintf("execute the transaction %s with data %v ", message.Handler, data)) 443 outputs, err := trancode.ExecutebyExternal(message.Handler, data, tx, mq.DocDBconn, mq.SignalRClient) 444 if err != nil { 445 mq.iLog.Error(fmt.Sprintf("Failed to execute transaction: %v", err)) 446 return err 447 } 448 449 err = tx.Commit() 450 if err != nil { 451 mq.iLog.Error(fmt.Sprintf("Failed to commit transaction: %v", err)) 452 return err 453 } 454 455 status := "Success" 456 errormessage := "" 457 if err != nil { 458 status = "Failed" 459 errormessage = err.Error() 460 } 461 mq.iLog.Debug(fmt.Sprintf("execute the message %s with data %s with handler %s with output %s", message.Id, message.PayLoad, message.Handler, outputs)) 462 463 message.CompletedOn = time.Now().UTC() 464 465 msghis := map[string]interface{}{ 466 "message": message, 467 "executedon": time.Now().UTC(), 468 "executedby": "System", 469 "status": status, 470 "errormessage": errormessage, 471 "messagequeue": mq.QueuName, 472 "outputs": outputs, 473 } 474 475 if mq.DocDBconn != nil { 476 477 _, err = mq.DocDBconn.InsertCollection("Job_History", msghis) 478 } 479 480 if status != "Success" && message.Execute < message.Retry { 481 message.Retry++ 482 mq.iLog.Debug(fmt.Sprintf("execute the message %s failed, and retry time: %d retry set value %d with data %s with handler %s", 483 message.Id, message.Execute, message.Retry, message.PayLoad, message.Handler)) 484 mq.Push(message) 485 } 486 487 return err 488 } 489 return nil 490 } 491 492 // bytesToMap converts a Message object to a map. 493 // It is currently unimplemented and will cause a panic. 494 func bytesToMap(message Message) { 495 panic("unimplemented") 496 } 497 498 // worker is a function that represents a worker in the MessageQueue. 499 // It processes messages from the jobs channel and calls the processMessage function for each message. 500 // The worker logs debug messages for its start, progress, and finish. 501 // It also logs performance metrics for the time taken to process the messages. 502 // If any panic occurs during message processing, it logs an error message. 503 // The worker waits for all jobs to be processed before returning. 504 // It is called by the execute method. 505 func (mq *MessageQueue) worker(id int, jobs <-chan Message, wg *sync.WaitGroup) { 506 /* startTime := time.Now() 507 defer func() { 508 elapsed := time.Since(startTime) 509 mq.iLog.PerformanceWithDuration("framework.queue.worker", elapsed) 510 }() 511 512 defer func() { 513 if r := recover(); r != nil { 514 mq.iLog.Error(fmt.Sprintf("framework.queue.processMessage failed to process message: %v", r)) 515 return 516 } 517 }() 518 */ 519 mq.iLog.Debug(fmt.Sprintf("worker %d started", id)) 520 mq.iLog.Debug(fmt.Sprintf("worker %d has %d jobs ", id, len(jobs))) 521 defer wg.Done() 522 mq.iLog.Debug(fmt.Sprintf("start process worker %d ", id)) 523 for msg := range jobs { 524 mq.iLog.Debug(fmt.Sprintf("worker %d started to process message %v", id, msg)) 525 mq.processMessage(msg) 526 } 527 mq.iLog.Debug(fmt.Sprintf("worker %d finished", id)) 528 wg.Wait() 529 wg.Done() 530 }