+67
@@ -0,0 +1,67 @@
|
||||
# Description
|
||||
|
||||
This module is used by the Status app to select mailservers based on their RTT(Round Trip Time).
|
||||
|
||||
It is exposed via the JSON RPC endpoint in the [`services/mailservers/tcp_ping.go`](../services/mailservers/tcp_ping.go) file.
|
||||
|
||||
# Usage
|
||||
|
||||
The simplest way to use the `mailserver_Ping` RPC command is using `curl`.
|
||||
|
||||
The call takes one struct argument which contains two attributes:
|
||||
|
||||
* `addresses` - A list of `enode` addresses to ping.
|
||||
* `timeoutMs` - Call timeout given in milliseconds.
|
||||
|
||||
The return value consists of a list of objects representing a result for each mailserver, each containing following attributes:
|
||||
|
||||
* `address` - The `enode` address of given mailserver.
|
||||
* `rttMs` - Round Trip Time given in milliseconds. Set to `null` in case of an error.`
|
||||
* `error` - A text of error that caused the ping failure.
|
||||
|
||||
# Example
|
||||
|
||||
```bash
|
||||
$ cat >payload.json <<EOL
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "mailservers_ping",
|
||||
"params": [
|
||||
{
|
||||
"addresses": [
|
||||
"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:443",
|
||||
"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:999"
|
||||
],
|
||||
"timeoutMs": 500
|
||||
}
|
||||
],
|
||||
"id": 1
|
||||
}
|
||||
EOL
|
||||
|
||||
$ curl -s localhost:8545 -H 'content-type: application/json' -d @payload.json
|
||||
```
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": [
|
||||
{
|
||||
"address": "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:443",
|
||||
"rttMs": 31,
|
||||
"error": null
|
||||
},
|
||||
{
|
||||
"address": "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:999",
|
||||
"rttMs": null,
|
||||
"error": "tcp check timeout: I/O timeout"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Links
|
||||
|
||||
* https://github.com/status-im/status-mobile/issues/9394
|
||||
* https://github.com/status-im/status-go/pull/1672
|
||||
* https://github.com/status-im/tcp-shaker
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package rtt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
tcp "github.com/status-im/tcp-shaker"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Addr string
|
||||
RTTMs int
|
||||
Err error
|
||||
}
|
||||
|
||||
// timeoutError indicates an error due to TCP connection timeout.
|
||||
// tcp-shaker returns an error implementing this interface in such a case.
|
||||
type timeoutError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
func runCheck(c *tcp.Checker, address string, timeout time.Duration) Result {
|
||||
// mesaure RTT
|
||||
start := time.Now()
|
||||
// TCP Ping
|
||||
err := c.CheckAddr(address, timeout)
|
||||
// measure RTT
|
||||
elapsed := time.Since(start)
|
||||
latency := int(elapsed.Nanoseconds() / 1e6)
|
||||
|
||||
if err != nil { // don't confuse users with valid latency values on error
|
||||
latency = -1
|
||||
switch err.(type) {
|
||||
case timeoutError:
|
||||
err = errors.Wrap(err, "tcp check timeout")
|
||||
case tcp.ErrConnect:
|
||||
err = errors.Wrap(err, "unable to connect")
|
||||
}
|
||||
}
|
||||
|
||||
return Result{
|
||||
Addr: address,
|
||||
RTTMs: latency,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func waitForResults(errCh <-chan error, resCh <-chan Result) (results []Result, err error) {
|
||||
for {
|
||||
select {
|
||||
case err = <-errCh:
|
||||
return nil, err
|
||||
case res, ok := <-resCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
results = append(results, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CheckHosts(addresses []string, timeout time.Duration) ([]Result, error) {
|
||||
c := tcp.NewChecker()
|
||||
|
||||
// channel for receiving possible checking loop failure
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
// stop the checking loop when function exists
|
||||
ctx, stopChecker := context.WithCancel(context.Background())
|
||||
defer stopChecker()
|
||||
|
||||
// loop that queries Epoll and pipes events to CheckAddr() calls
|
||||
go func() {
|
||||
errCh <- c.CheckingLoop(ctx)
|
||||
}()
|
||||
// wait for CheckingLoop to prepare the epoll/kqueue
|
||||
<-c.WaitReady()
|
||||
|
||||
// channel for returning results from concurrent checks
|
||||
resCh := make(chan Result, len(addresses))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(addresses); i++ {
|
||||
wg.Add(1)
|
||||
go func(address string, resCh chan<- Result) {
|
||||
defer wg.Done()
|
||||
resCh <- runCheck(c, address, timeout)
|
||||
}(addresses[i], resCh)
|
||||
}
|
||||
// wait for all the routines to finish before closing results channel
|
||||
wg.Wait()
|
||||
close(resCh)
|
||||
|
||||
// wait for the results for all addresses or a checking loop error
|
||||
return waitForResults(errCh, resCh)
|
||||
}
|
||||
Reference in New Issue
Block a user