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(&registerConsumer{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  }