github.com/status-im/status-go@v1.1.0/rtt/rtt.go (about)

     1  package rtt
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	errors "github.com/pkg/errors"
     9  	tcp "github.com/status-im/tcp-shaker"
    10  )
    11  
    12  type Result struct {
    13  	Addr  string
    14  	RTTMs int
    15  	Err   error
    16  }
    17  
    18  // timeoutError indicates an error due to TCP connection timeout.
    19  // tcp-shaker returns an error implementing this interface in such a case.
    20  type timeoutError interface {
    21  	Timeout() bool
    22  }
    23  
    24  func runCheck(c *tcp.Checker, address string, timeout time.Duration) Result {
    25  	// mesaure RTT
    26  	start := time.Now()
    27  	// TCP Ping
    28  	err := c.CheckAddr(address, timeout)
    29  	// measure RTT
    30  	elapsed := time.Since(start)
    31  	latency := int(elapsed.Nanoseconds() / 1e6)
    32  
    33  	if err != nil { // don't confuse users with valid latency values on error
    34  		latency = -1
    35  		switch err.(type) {
    36  		case timeoutError:
    37  			err = errors.Wrap(err, "tcp check timeout")
    38  		case tcp.ErrConnect:
    39  			err = errors.Wrap(err, "unable to connect")
    40  		}
    41  	}
    42  
    43  	return Result{
    44  		Addr:  address,
    45  		RTTMs: latency,
    46  		Err:   err,
    47  	}
    48  }
    49  
    50  func waitForResults(errCh <-chan error, resCh <-chan Result) (results []Result, err error) {
    51  	for {
    52  		select {
    53  		case err = <-errCh:
    54  			return nil, err
    55  		case res, ok := <-resCh:
    56  			if !ok {
    57  				return
    58  			}
    59  			results = append(results, res)
    60  		}
    61  	}
    62  }
    63  
    64  func CheckHosts(addresses []string, timeout time.Duration) ([]Result, error) {
    65  	c := tcp.NewChecker()
    66  
    67  	// channel for receiving possible checking loop failure
    68  	errCh := make(chan error, 1)
    69  
    70  	// stop the checking loop when function exists
    71  	ctx, stopChecker := context.WithCancel(context.Background())
    72  	defer stopChecker()
    73  
    74  	// loop that queries Epoll and pipes events to CheckAddr() calls
    75  	go func() {
    76  		errCh <- c.CheckingLoop(ctx)
    77  	}()
    78  	// wait for CheckingLoop to prepare the epoll/kqueue
    79  	<-c.WaitReady()
    80  
    81  	// channel for returning results from concurrent checks
    82  	resCh := make(chan Result, len(addresses))
    83  
    84  	var wg sync.WaitGroup
    85  	for i := 0; i < len(addresses); i++ {
    86  		wg.Add(1)
    87  		go func(address string, resCh chan<- Result) {
    88  			defer wg.Done()
    89  			resCh <- runCheck(c, address, timeout)
    90  		}(addresses[i], resCh)
    91  	}
    92  	// wait for all the routines to finish before closing results channel
    93  	wg.Wait()
    94  	close(resCh)
    95  
    96  	// wait for the results for all addresses or a checking loop error
    97  	return waitForResults(errCh, resCh)
    98  }