github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/discovery/brokerdiscovery/repository.go (about) 1 /* 2 * Copyright (C) 2019 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package brokerdiscovery 19 20 import ( 21 "sync" 22 "time" 23 24 "github.com/mysteriumnetwork/node/communication" 25 "github.com/mysteriumnetwork/node/communication/nats" 26 "github.com/mysteriumnetwork/node/core/discovery/proposal" 27 "github.com/mysteriumnetwork/node/market" 28 ) 29 30 // Repository provides proposals from the broker. 31 type Repository struct { 32 storage *ProposalStorage 33 receiver communication.Receiver 34 timeoutInterval time.Duration 35 36 stopOnce sync.Once 37 stopChan chan struct{} 38 39 timeoutCheckStep time.Duration 40 watchdogLock sync.Mutex 41 timeoutCheckSeens map[market.ProposalID]time.Time 42 } 43 44 // NewRepository constructs a new proposal repository (backed by the broker). 45 func NewRepository( 46 connection nats.Connection, 47 storage *ProposalStorage, 48 proposalTimeoutInterval time.Duration, 49 proposalCheckInterval time.Duration, 50 ) *Repository { 51 return &Repository{ 52 storage: storage, 53 receiver: nats.NewReceiver(connection, communication.NewCodecJSON(), "*"), 54 timeoutInterval: proposalTimeoutInterval, 55 56 stopChan: make(chan struct{}), 57 timeoutCheckStep: proposalCheckInterval, 58 timeoutCheckSeens: make(map[market.ProposalID]time.Time), 59 } 60 } 61 62 // Proposal returns a single proposal by its ID. 63 func (r *Repository) Proposal(id market.ProposalID) (*market.ServiceProposal, error) { 64 return r.storage.GetProposal(id) 65 } 66 67 // Proposals returns proposals matching the filter. 68 func (r *Repository) Proposals(filter *proposal.Filter) ([]market.ServiceProposal, error) { 69 return r.storage.FindProposals(filter) 70 } 71 72 // Countries returns proposals per country matching the filter. 73 func (r *Repository) Countries(filter *proposal.Filter) (map[string]int, error) { 74 return r.storage.Countries(filter) 75 } 76 77 // Start begins proposals synchronization to storage 78 func (r *Repository) Start() error { 79 err := r.receiver.Receive(®isterConsumer{Callback: r.proposalRegisterMessage}) 80 if err != nil { 81 return err 82 } 83 84 err = r.receiver.Receive(&unregisterConsumer{Callback: r.proposalUnregisterMessage}) 85 if err != nil { 86 return err 87 } 88 89 err = r.receiver.Receive(&pingConsumer{Callback: r.proposalPingMessage}) 90 if err != nil { 91 return err 92 } 93 94 go r.timeoutCheckLoop() 95 96 return nil 97 } 98 99 // Stop ends proposals synchronization to storage 100 func (r *Repository) Stop() { 101 r.stopOnce.Do(func() { 102 close(r.stopChan) 103 104 r.receiver.ReceiveUnsubscribe(pingEndpoint) 105 r.receiver.ReceiveUnsubscribe(unregisterEndpoint) 106 r.receiver.ReceiveUnsubscribe(registerEndpoint) 107 }) 108 } 109 110 func (r *Repository) proposalRegisterMessage(message registerMessage) error { 111 if !message.Proposal.IsSupported() { 112 return nil 113 } 114 115 r.storage.AddProposal(message.Proposal) 116 117 r.watchdogLock.Lock() 118 defer r.watchdogLock.Unlock() 119 r.timeoutCheckSeens[message.Proposal.UniqueID()] = time.Now().UTC() 120 121 return nil 122 } 123 124 func (r *Repository) proposalUnregisterMessage(message unregisterMessage) error { 125 r.storage.RemoveProposal(message.Proposal.UniqueID()) 126 127 r.watchdogLock.Lock() 128 defer r.watchdogLock.Unlock() 129 delete(r.timeoutCheckSeens, message.Proposal.UniqueID()) 130 131 return nil 132 } 133 134 func (r *Repository) proposalPingMessage(message pingMessage) error { 135 if !message.Proposal.IsSupported() { 136 return nil 137 } 138 139 r.storage.AddProposal(message.Proposal) 140 141 r.watchdogLock.Lock() 142 defer r.watchdogLock.Unlock() 143 r.timeoutCheckSeens[message.Proposal.UniqueID()] = time.Now() 144 145 return nil 146 } 147 148 func (r *Repository) timeoutCheckLoop() { 149 for { 150 select { 151 case <-r.stopChan: 152 return 153 case <-time.After(r.timeoutCheckStep): 154 r.watchdogLock.Lock() 155 for proposalID, proposalSeen := range r.timeoutCheckSeens { 156 if time.Now().After(proposalSeen.Add(r.timeoutInterval)) { 157 r.storage.RemoveProposal(proposalID) 158 delete(r.timeoutCheckSeens, proposalID) 159 } 160 } 161 r.watchdogLock.Unlock() 162 } 163 } 164 }