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  }