neo-go/_pkg.dev/connmgr/connmgr.go
Roman Khimov ddd1d92ff1 pkg: hide it by moving to _pkg.dev
The idea here is to preserve the history of `dev` branch development and its
code when merging with the `master`. Later this code could be moved into the
masters code where appropriate.
2019-08-20 18:39:50 +03:00

245 lines
5.2 KiB
Go
Executable file

package connmgr
import (
"errors"
"fmt"
"net"
"net/http"
"time"
)
var (
// maxOutboundConn is the maximum number of active peers
// that the connection manager will try to have
maxOutboundConn = 10
// maxRetries is the maximum amount of successive retries that
// we can have before we stop dialing that peer
maxRetries = uint8(5)
)
// Connmgr manages pending/active/failed cnnections
type Connmgr struct {
config Config
PendingList map[string]*Request
ConnectedList map[string]*Request
actionch chan func()
}
//New creates a new connection manager
func New(cfg Config) (*Connmgr, error) {
listener, err := net.Listen("tcp", cfg.AddressPort)
if err != nil {
return nil, err
}
cnnmgr := &Connmgr{
cfg,
make(map[string]*Request),
make(map[string]*Request),
make(chan func(), 300),
}
go func() {
defer func() {
listener.Close()
}()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go cfg.OnAccept(conn)
}
}()
return cnnmgr, nil
}
// NewRequest will make a new connection gets the address from address func in config
// Then dials it and assigns it to pending
func (c *Connmgr) NewRequest() error {
// Fetch address
addr, err := c.config.GetAddress()
if err != nil {
return fmt.Errorf("error getting address " + err.Error())
}
r := &Request{
Addr: addr,
}
return c.Connect(r)
}
// Connect will dial the address in the Request
// Updating the request object depending on the outcome
func (c *Connmgr) Connect(r *Request) error {
r.Retries++
conn, err := c.dial(r.Addr)
if err != nil {
c.failed(r)
return err
}
r.Conn = conn
r.Inbound = true
// r.Permanent is set by the address manager/caller. default is false
// The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network
// or are reliable. The connmgr will be more leniennt to permanent addresses as they have
// a track record or reputation of being reliable.
return c.connected(r)
}
//Disconnect will remove the request from the connected/pending list and close the connection
func (c *Connmgr) Disconnect(addr string) {
var r *Request
// fetch from connected list
r, ok := c.ConnectedList[addr]
if !ok {
// If not in connected, check pending
r, _ = c.PendingList[addr]
}
c.disconnected(r)
}
// Dial is used to dial up connections given the addres and ip in the form address:port
func (c *Connmgr) dial(addr string) (net.Conn, error) {
dialTimeout := 1 * time.Second
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
if !isConnected() {
return nil, errors.New("Fatal Error: You do not seem to be connected to the internet")
}
return conn, err
}
return conn, nil
}
func (c *Connmgr) failed(r *Request) {
c.actionch <- func() {
// priority to check if it is permanent or inbound
// if so then these peers are valuable in NEO and so we will just retry another time
if r.Inbound || r.Permanent {
multiplier := time.Duration(r.Retries * 10)
time.AfterFunc(multiplier*time.Second,
func() {
c.Connect(r)
},
)
// if not then we should check if this request has had maxRetries
// if it has then get a new address
// if not then call Connect on it again
} else if r.Retries > maxRetries {
if c.config.GetAddress != nil {
go c.NewRequest()
}
} else {
go c.Connect(r)
}
}
}
// Disconnected is called when a peer disconnects.
// we take the addr from peer, which is also it's key in the map
// and we use it to remove it from the connectedList
func (c *Connmgr) disconnected(r *Request) error {
if r == nil {
// if object is nil, we return nil
return nil
}
// if for some reason the underlying connection is not closed, close it
err := r.Conn.Close()
if err != nil {
return err
}
// remove from any pending/connected list
delete(c.PendingList, r.Addr)
delete(c.ConnectedList, r.Addr)
// If permanent,then lets retry
if r.Permanent {
return c.Connect(r)
}
return nil
}
//Connected is called when the connection manager makes a successful connection.
func (c *Connmgr) connected(r *Request) error {
// This should not be the case, since we connected
if r == nil {
return errors.New("request object as nil inside of the connected function")
}
// reset retries to 0
r.Retries = 0
// add to connectedList
c.ConnectedList[r.Addr] = r
// remove from pending if it was there
delete(c.PendingList, r.Addr)
if c.config.OnConnection != nil {
c.config.OnConnection(r.Conn, r.Addr)
}
return nil
}
// Pending is synchronous, we do not want to continue with logic
// until we are certain it has been added to the pendingList
func (c *Connmgr) pending(r *Request) error {
if r == nil {
return errors.New("request object is nil")
}
c.PendingList[r.Addr] = r
return nil
}
// Run will start the connection manager
func (c *Connmgr) Run() error {
fmt.Println("Connection manager started")
go c.loop()
return nil
}
func (c *Connmgr) loop() {
for {
select {
case f := <-c.actionch:
f()
}
}
}
// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application
func isConnected() (ok bool) {
_, err := http.Get("http://clients3.google.com/generate_204")
if err != nil {
return false
}
return true
}