manager.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package runtime
  2. import (
  3. "errors"
  4. "sync"
  5. "github.com/mhsanaei/3x-ui/v3/database"
  6. "github.com/mhsanaei/3x-ui/v3/database/model"
  7. )
  8. // Manager is the entry point for service code that needs a Runtime.
  9. // One singleton lives in the package-level `manager` var, set at
  10. // server bootstrap (web.go calls SetManager once). InboundService and
  11. // friends read it via GetManager().
  12. //
  13. // Local runs forever; Remotes are built lazily per nodeID and cached.
  14. // Cache invalidation runs on node Update/Delete (NodeService hooks
  15. // InvalidateNode) so a token rotation surfaces the next call.
  16. type Manager struct {
  17. local Runtime
  18. mu sync.RWMutex
  19. remotes map[int]*Remote
  20. }
  21. // NewManager wires the singleton with the deps Local needs. The runtime
  22. // package can't import service so the caller (web.go) supplies the
  23. // callbacks that bridge into XrayService.
  24. func NewManager(localDeps LocalDeps) *Manager {
  25. return &Manager{
  26. local: NewLocal(localDeps),
  27. remotes: make(map[int]*Remote),
  28. }
  29. }
  30. // RuntimeFor picks the right adapter for an inbound based on NodeID.
  31. // Returns local when nodeID is nil; otherwise looks up the node row
  32. // (or returns the cached Remote for it). The caller does not need to
  33. // know which kind they got — that's the point of the abstraction.
  34. func (m *Manager) RuntimeFor(nodeID *int) (Runtime, error) {
  35. if nodeID == nil {
  36. return m.local, nil
  37. }
  38. m.mu.RLock()
  39. if rt, ok := m.remotes[*nodeID]; ok {
  40. m.mu.RUnlock()
  41. return rt, nil
  42. }
  43. m.mu.RUnlock()
  44. // Cache miss — load the node row and build a Remote. We re-check
  45. // under the write lock to avoid duplicate construction under load.
  46. m.mu.Lock()
  47. defer m.mu.Unlock()
  48. if rt, ok := m.remotes[*nodeID]; ok {
  49. return rt, nil
  50. }
  51. n, err := loadNode(*nodeID)
  52. if err != nil {
  53. return nil, err
  54. }
  55. if !n.Enable {
  56. return nil, errors.New("node " + n.Name + " is disabled")
  57. }
  58. rt := NewRemote(n)
  59. m.remotes[*nodeID] = rt
  60. return rt, nil
  61. }
  62. // Local returns the singleton local runtime. Used by code that needs
  63. // to operate on the panel's own xray regardless of which inbound it
  64. // came from (e.g. on-demand restart from the UI).
  65. func (m *Manager) Local() Runtime { return m.local }
  66. // RemoteFor returns the Remote adapter for an already-loaded node row.
  67. // Differs from RuntimeFor in two ways: it skips the DB lookup (caller
  68. // hands in the node), and it returns the concrete *Remote so callers
  69. // like NodeTrafficSyncJob can reach FetchTrafficSnapshot, which the
  70. // Runtime interface doesn't expose.
  71. func (m *Manager) RemoteFor(node *model.Node) (*Remote, error) {
  72. if node == nil {
  73. return nil, errors.New("node is nil")
  74. }
  75. m.mu.RLock()
  76. if rt, ok := m.remotes[node.Id]; ok {
  77. m.mu.RUnlock()
  78. return rt, nil
  79. }
  80. m.mu.RUnlock()
  81. m.mu.Lock()
  82. defer m.mu.Unlock()
  83. if rt, ok := m.remotes[node.Id]; ok {
  84. return rt, nil
  85. }
  86. rt := NewRemote(node)
  87. m.remotes[node.Id] = rt
  88. return rt, nil
  89. }
  90. // InvalidateNode drops the cached Remote for nodeID so the next
  91. // RuntimeFor call rebuilds it from the (possibly updated) node row.
  92. // Called from NodeService.Update / Delete.
  93. func (m *Manager) InvalidateNode(nodeID int) {
  94. m.mu.Lock()
  95. defer m.mu.Unlock()
  96. delete(m.remotes, nodeID)
  97. }
  98. // loadNode reads a node row directly from the DB. Kept package-local
  99. // to avoid pulling NodeService into the runtime — service depends on
  100. // runtime, not the other way around.
  101. func loadNode(id int) (*model.Node, error) {
  102. db := database.GetDB()
  103. n := &model.Node{}
  104. if err := db.Model(model.Node{}).Where("id = ?", id).First(n).Error; err != nil {
  105. return nil, err
  106. }
  107. return n, nil
  108. }
  109. // Singleton wiring -------------------------------------------------------
  110. var (
  111. managerMu sync.RWMutex
  112. manager *Manager
  113. )
  114. // SetManager installs the process-wide Manager. web.go calls this once
  115. // during NewServer. Tests can call it again with a stub.
  116. func SetManager(m *Manager) {
  117. managerMu.Lock()
  118. defer managerMu.Unlock()
  119. manager = m
  120. }
  121. // GetManager returns the installed Manager, or nil before SetManager
  122. // has run. Callers should treat nil as "still booting" — the existing
  123. // behaviour for code paths that only run on the local engine continues
  124. // to work via a pre-wired fallback set up in init() below.
  125. func GetManager() *Manager {
  126. managerMu.RLock()
  127. defer managerMu.RUnlock()
  128. return manager
  129. }