github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/service/pool.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 service 19 20 import ( 21 "sync" 22 23 "github.com/jinzhu/copier" 24 "github.com/pkg/errors" 25 "github.com/rs/zerolog/log" 26 27 "github.com/mysteriumnetwork/node/core/policy" 28 "github.com/mysteriumnetwork/node/core/service/servicestate" 29 "github.com/mysteriumnetwork/node/identity" 30 "github.com/mysteriumnetwork/node/market" 31 "github.com/mysteriumnetwork/node/p2p" 32 "github.com/mysteriumnetwork/node/utils" 33 ) 34 35 // ID represent unique identifier of the running service. 36 type ID string 37 38 // Pool is responsible for supervising running instances 39 type Pool struct { 40 eventPublisher Publisher 41 instances map[ID]*Instance 42 sync.Mutex 43 } 44 45 // Publisher is responsible for publishing given events 46 type Publisher interface { 47 Publish(topic string, data interface{}) 48 } 49 50 // NewPool returns a empty service pool 51 func NewPool(eventPublisher Publisher) *Pool { 52 return &Pool{ 53 eventPublisher: eventPublisher, 54 instances: make(map[ID]*Instance), 55 } 56 } 57 58 // Add registers a service to running instances pool 59 func (p *Pool) Add(instance *Instance) { 60 p.Lock() 61 defer p.Unlock() 62 63 p.instances[instance.ID] = instance 64 } 65 66 // Del removes a service from running instances pool 67 func (p *Pool) Del(id ID) { 68 p.Lock() 69 defer p.Unlock() 70 p.del(id) 71 } 72 73 func (p *Pool) del(id ID) { 74 delete(p.instances, id) 75 } 76 77 // ErrNoSuchInstance represents the error when we're stopping an instance that does not exist 78 var ErrNoSuchInstance = errors.New("no such instance") 79 80 // Stop kills all sub-resources of instance 81 func (p *Pool) Stop(id ID) error { 82 p.Lock() 83 defer p.Unlock() 84 return p.stop(id) 85 } 86 87 func (p *Pool) stop(id ID) error { 88 instance, ok := p.instances[id] 89 if !ok { 90 return ErrNoSuchInstance 91 } 92 p.del(id) 93 return instance.stop() 94 } 95 96 // StopAll kills all running instances 97 func (p *Pool) StopAll() error { 98 p.Lock() 99 defer p.Unlock() 100 errStop := utils.ErrorCollection{} 101 for id := range p.instances { 102 errStop.Add(p.stop(id)) 103 } 104 105 return errStop.Errorf("Some instances did not stop: %v", ". ") 106 } 107 108 // List returns all running service instances. 109 func (p *Pool) List() []*Instance { 110 p.Lock() 111 defer p.Unlock() 112 113 list := make([]*Instance, 0, len(p.instances)) 114 for _, instance := range p.instances { 115 list = append(list, instance) 116 } 117 118 return list 119 } 120 121 // Instance returns service instance by the requested id. 122 func (p *Pool) Instance(id ID) *Instance { 123 p.Lock() 124 defer p.Unlock() 125 return p.instances[id] 126 } 127 128 // NewInstance creates new instance of the service. 129 func NewInstance( 130 providerID identity.Identity, 131 serviceType string, 132 options Options, 133 proposal market.ServiceProposal, 134 state servicestate.State, 135 service Service, 136 policyProvider policy.Provider, 137 discovery Discovery, 138 ) *Instance { 139 return &Instance{ 140 ProviderID: providerID, 141 Type: serviceType, 142 Options: options, 143 Proposal: proposal, 144 state: state, 145 service: service, 146 policyProvider: policyProvider, 147 discovery: discovery, 148 } 149 } 150 151 // Instance represents a run service 152 type Instance struct { 153 ID ID 154 state servicestate.State 155 stateLock sync.RWMutex 156 ProviderID identity.Identity 157 Type string 158 Options Options 159 service Service 160 161 muProposal sync.Mutex 162 Proposal market.ServiceProposal 163 policyProvider policy.Provider 164 discovery Discovery 165 eventPublisher Publisher 166 p2pChannelsLock sync.Mutex 167 p2pChannels []p2p.Channel 168 location locationResolver 169 } 170 171 // Service returns the running service implementation. 172 func (i *Instance) Service() Service { 173 return i.service 174 } 175 176 // PolicyProvider returns policy provider implementation. 177 func (i *Instance) PolicyProvider() policy.Provider { 178 return i.policyProvider 179 } 180 181 // State returns the service instance state. 182 func (i *Instance) State() servicestate.State { 183 i.stateLock.RLock() 184 defer i.stateLock.RUnlock() 185 return i.state 186 } 187 188 func (i *Instance) proposalWithCurrentLocation() market.ServiceProposal { 189 location, err := i.location.DetectLocation() 190 if err != nil { 191 log.Warn().Err(err).Msg("Failed to get current location for proposal, using last known location") 192 return i.Proposal 193 } 194 195 i.muProposal.Lock() 196 i.Proposal.Location = *market.NewLocation(location) 197 i.muProposal.Unlock() 198 199 return i.Proposal 200 } 201 202 func (i *Instance) setState(newState servicestate.State) { 203 i.stateLock.Lock() 204 defer i.stateLock.Unlock() 205 i.state = newState 206 207 i.eventPublisher.Publish(servicestate.AppTopicServiceStatus, i.toEvent()) 208 } 209 210 func (i *Instance) addP2PChannel(ch p2p.Channel) { 211 i.p2pChannelsLock.Lock() 212 defer i.p2pChannelsLock.Unlock() 213 214 i.p2pChannels = append(i.p2pChannels, ch) 215 } 216 217 func (i *Instance) stop() error { 218 errStop := utils.ErrorCollection{} 219 if i.discovery != nil { 220 i.discovery.Stop() 221 } 222 if i.service != nil { 223 errStop.Add(i.service.Stop()) 224 } 225 226 i.p2pChannelsLock.Lock() 227 for _, channel := range i.p2pChannels { 228 errStop.Add(channel.Close()) 229 } 230 i.p2pChannelsLock.Unlock() 231 232 i.setState(servicestate.NotRunning) 233 return errStop.Errorf("ErrorCollection(%s)", ", ") 234 } 235 236 // toEvent returns an event representation of the instance 237 func (i *Instance) toEvent() servicestate.AppEventServiceStatus { 238 return servicestate.AppEventServiceStatus{ 239 ID: string(i.ID), 240 ProviderID: i.Proposal.ProviderID, 241 Type: i.Proposal.ServiceType, 242 Status: string(i.state), 243 } 244 } 245 246 // CopyProposal returns a copy of Proposal 247 func (i *Instance) CopyProposal() market.ServiceProposal { 248 i.muProposal.Lock() 249 defer i.muProposal.Unlock() 250 251 var proposal market.ServiceProposal 252 if err := copier.CopyWithOption(&proposal, i.Proposal, copier.Option{DeepCopy: true}); err != nil { 253 panic(err) 254 } 255 // workaround b/c of copier bug: it make empty slice instead of nil 256 if i.Proposal.Contacts == nil { 257 proposal.Contacts = nil 258 } 259 260 return proposal 261 }