1
0

node_reset_queue.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package service
  2. import (
  3. "context"
  4. "sync"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  9. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  10. "gorm.io/gorm"
  11. "gorm.io/gorm/clause"
  12. )
  13. // nodeBulkResetter is a node runtime that can zero many clients in one call.
  14. type nodeBulkResetter interface {
  15. ResetClientTraffics(ctx context.Context, emails []string) error
  16. }
  17. type nodeEmail struct {
  18. NodeId int `gorm:"column:node_id"`
  19. Email string `gorm:"column:email"`
  20. }
  21. // queueNodeResets records a reset for every node hosting one of emails (all
  22. // node-hosted clients when emails is nil) and returns the nodes involved.
  23. func queueNodeResets(tx *gorm.DB, emails []string) ([]int, error) {
  24. base := func() *gorm.DB {
  25. return tx.Table("clients").
  26. Select("DISTINCT inbounds.node_id AS node_id, clients.email AS email").
  27. Joins("JOIN client_inbounds ON client_inbounds.client_id = clients.id").
  28. Joins("JOIN inbounds ON inbounds.id = client_inbounds.inbound_id").
  29. Where("inbounds.node_id IS NOT NULL")
  30. }
  31. var pairs []nodeEmail
  32. if emails == nil {
  33. if err := base().Scan(&pairs).Error; err != nil {
  34. return nil, err
  35. }
  36. } else {
  37. for _, batch := range chunkStrings(uniqueNonEmptyStrings(emails), sqlInChunk) {
  38. var page []nodeEmail
  39. if err := base().Where("clients.email IN ?", batch).Scan(&page).Error; err != nil {
  40. return nil, err
  41. }
  42. pairs = append(pairs, page...)
  43. }
  44. }
  45. if len(pairs) == 0 {
  46. return nil, nil
  47. }
  48. now := time.Now().UnixNano()
  49. rows := make([]model.NodePendingReset, 0, len(pairs))
  50. nodes := make(map[int]struct{})
  51. for _, p := range pairs {
  52. rows = append(rows, model.NodePendingReset{NodeId: p.NodeId, Email: p.Email, QueuedAt: now})
  53. nodes[p.NodeId] = struct{}{}
  54. }
  55. if err := tx.Clauses(clause.OnConflict{
  56. Columns: []clause.Column{{Name: "node_id"}, {Name: "email"}},
  57. DoUpdates: clause.AssignmentColumns([]string{"queued_at"}),
  58. }).CreateInBatches(rows, 200).Error; err != nil {
  59. return nil, err
  60. }
  61. ids := make([]int, 0, len(nodes))
  62. for id := range nodes {
  63. ids = append(ids, id)
  64. }
  65. return ids, nil
  66. }
  67. // pendingNodeResetEmails lists the clients whose reset the node still owes.
  68. func pendingNodeResetEmails(tx *gorm.DB, nodeID int) (map[string]struct{}, error) {
  69. var emails []string
  70. if err := tx.Model(&model.NodePendingReset{}).Where("node_id = ?", nodeID).Pluck("email", &emails).Error; err != nil {
  71. return nil, err
  72. }
  73. out := make(map[string]struct{}, len(emails))
  74. for _, e := range emails {
  75. out[e] = struct{}{}
  76. }
  77. return out, nil
  78. }
  79. var nodeResetDeliveryLocks sync.Map
  80. // DeliverNodeResets sends the node every reset it has not confirmed. A row is
  81. // dropped only after the node accepted it and only if nothing re-queued it since.
  82. func (s *InboundService) DeliverNodeResets(ctx context.Context, nodeID int, rt runtime.Runtime) error {
  83. lock, _ := nodeResetDeliveryLocks.LoadOrStore(nodeID, &sync.Mutex{})
  84. lock.(*sync.Mutex).Lock()
  85. defer lock.(*sync.Mutex).Unlock()
  86. db := database.GetDB()
  87. var rows []model.NodePendingReset
  88. if err := db.Where("node_id = ?", nodeID).Order("id").Find(&rows).Error; err != nil {
  89. return err
  90. }
  91. if len(rows) == 0 {
  92. return nil
  93. }
  94. bulk, canBulk := rt.(nodeBulkResetter)
  95. for start := 0; start < len(rows); start += sqlInChunk {
  96. batch := rows[start:min(start+sqlInChunk, len(rows))]
  97. emails := make([]string, len(batch))
  98. for i := range batch {
  99. emails[i] = batch[i].Email
  100. }
  101. var err error
  102. if canBulk && len(batch) > nodeBulkPushThreshold {
  103. err = bulk.ResetClientTraffics(ctx, emails)
  104. } else {
  105. for _, email := range emails {
  106. if err = rt.ResetClientTraffic(ctx, nil, email); err != nil {
  107. break
  108. }
  109. }
  110. }
  111. if err != nil {
  112. return err
  113. }
  114. for i := range batch {
  115. if err := db.Where("id = ? AND queued_at = ?", batch[i].Id, batch[i].QueuedAt).
  116. Delete(&model.NodePendingReset{}).Error; err != nil {
  117. return err
  118. }
  119. }
  120. }
  121. return nil
  122. }
  123. // deliverNodeResetsNow tries each node once right after a reset commits; what
  124. // fails stays queued for the node sync job.
  125. func (s *InboundService) deliverNodeResetsNow(nodeIDs []int) {
  126. mgr := runtime.GetManager()
  127. if mgr == nil || len(nodeIDs) == 0 {
  128. return
  129. }
  130. fanoutInboundResults(nodeIDs, nodeFanoutConcurrency, func(i int) struct{} {
  131. rt, err := mgr.RuntimeFor(&nodeIDs[i])
  132. if err != nil {
  133. return struct{}{}
  134. }
  135. ctx, cancel := nodePushContext()
  136. defer cancel()
  137. if err := s.DeliverNodeResets(ctx, nodeIDs[i], rt); err != nil {
  138. logger.Warning("reset delivery to", rt.Name(), "deferred to the next sync:", err)
  139. }
  140. return struct{}{}
  141. })
  142. }