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  }