2023-02-27 06:04:26 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-02-28 06:33:35 +00:00
|
|
|
"fmt"
|
2023-02-27 06:04:26 +00:00
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/g3n/engine/core"
|
|
|
|
"github.com/g3n/engine/geometry"
|
|
|
|
"github.com/g3n/engine/graphic"
|
|
|
|
"github.com/g3n/engine/material"
|
|
|
|
"github.com/g3n/engine/math32"
|
|
|
|
)
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
var (
|
|
|
|
dials []Dial
|
|
|
|
)
|
2023-02-27 06:04:26 +00:00
|
|
|
|
|
|
|
type Dial struct {
|
2023-02-28 06:33:35 +00:00
|
|
|
Connection *net.TCPConn // the real deal underlying TCP connection
|
2023-02-27 06:04:26 +00:00
|
|
|
Geo *geometry.Geometry // the 3D geometry
|
|
|
|
Mat *material.Standard // the material / color
|
|
|
|
Mesh *graphic.Mesh // the combined geometry and material
|
|
|
|
Node *core.Node // pointer to node in scene, required for removing the object if connection goes down
|
2023-02-28 06:33:35 +00:00
|
|
|
Status string // String to control what's displayed in the scene. Can be Disconnected, Connected, Leased Out
|
2023-02-27 06:04:26 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
// TBD: Make less destructive when downsizing (prioritize dials without connections, then dials that aren't leased out, then what's left)
|
|
|
|
func setConnPoolSize(maxPoolSize int) {
|
|
|
|
|
|
|
|
for _, dial := range dials {
|
|
|
|
scene.Remove(dial.Mesh)
|
|
|
|
dial.Geo = nil
|
|
|
|
dial.Mat = nil
|
|
|
|
dial.Mesh = nil
|
|
|
|
dial.Node = nil
|
|
|
|
}
|
2023-02-27 06:04:26 +00:00
|
|
|
|
|
|
|
dials = make([]Dial, 0)
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
// Update the position of the connections on the scene
|
|
|
|
padding := float32(200 / (maxPoolSize + 1)) // space between the connection objects in 3D space
|
|
|
|
currX := float32(maxPoolSize/2) * -padding // variable gets updated, this is the initial starting position (left most connection in 3D space)
|
|
|
|
if maxPoolSize%2 == 0 {
|
|
|
|
currX += padding / 2
|
|
|
|
}
|
|
|
|
for i := 0; i < maxPoolSize; i++ {
|
|
|
|
dial := Dial{Geo: geometry.NewBox(1, 1, 1), Mat: material.NewStandard(math32.NewColor("Grey")), Status: "Disconnected"}
|
|
|
|
dial.Mesh = graphic.NewMesh(dial.Geo, dial.Mat)
|
|
|
|
dial.Mesh.SetPositionX(currX)
|
|
|
|
currX += padding
|
|
|
|
dial.Node = scene.Add(dial.Mesh)
|
|
|
|
dials = append(dials, dial)
|
|
|
|
}
|
|
|
|
updateSceneConnections()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keeps the pool full, replaces stale connections, and at the same time creates the objects in the 3D scenes representing physical connections
|
|
|
|
// should be ran in its own goroutine
|
|
|
|
func connPoolWatchdog(serverAddress string) {
|
2023-02-27 06:04:26 +00:00
|
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
stateChange := false // set to true if there's been any state changes since the last polling
|
|
|
|
|
2023-02-27 06:04:26 +00:00
|
|
|
// Check status of existing open connections in pool
|
2023-02-28 06:33:35 +00:00
|
|
|
for i := range dials {
|
|
|
|
|
|
|
|
// if there is no existing connection
|
|
|
|
if dials[i].Connection == nil {
|
|
|
|
log.Printf("dialing new connection")
|
|
|
|
var err error
|
|
|
|
dials[i].Connection, err = net.DialTCP("tcp", nil, tcpAddr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("failed to dial TCP connection: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
dials[i].Connection.SetKeepAlive(true)
|
|
|
|
dials[i].Connection.SetKeepAlivePeriod(time.Second * 5)
|
|
|
|
dials[i].Status = "Connected"
|
|
|
|
stateChange = true
|
|
|
|
|
2023-02-27 06:08:36 +00:00
|
|
|
continue
|
2023-02-27 06:04:26 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
// test if the connection is still working
|
|
|
|
if !isConnUp(dials[i].Connection) { // TBD: this should be moved to check before first write to the socket file descriptor instead of polling here
|
|
|
|
log.Printf("closing bad idle connection and removing from pool - %s", dials[i].Connection.LocalAddr().String())
|
|
|
|
dials[i].Connection.Close()
|
|
|
|
dials[i].Connection = nil
|
|
|
|
dials[i].Status = "Disconnected"
|
|
|
|
stateChange = true
|
2023-02-27 06:04:26 +00:00
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
continue
|
|
|
|
}
|
2023-02-27 06:04:26 +00:00
|
|
|
|
|
|
|
}
|
2023-02-28 06:33:35 +00:00
|
|
|
|
|
|
|
if stateChange {
|
|
|
|
updateSceneConnections()
|
2023-02-27 06:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(time.Second * 5) // random sleep, AKA evidence this shouldn't be a watchdog,
|
|
|
|
// this whole function should be event based instead of polling
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-28 06:33:35 +00:00
|
|
|
// TBD: Refactor to buffered channel instead of finding one on the fly?
|
|
|
|
func requestLease() (*net.TCPConn, error) {
|
|
|
|
inPoolConns := make(map[int]Dial)
|
|
|
|
for i, dial := range dials {
|
|
|
|
if dial.Status == "Connected" {
|
|
|
|
inPoolConns[i] = dial
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(inPoolConns) == 0 {
|
|
|
|
return nil, fmt.Errorf("pool exhausted")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Selects a random connection from the pool
|
|
|
|
var i int
|
|
|
|
for i = range inPoolConns {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
dials[i].Status = "Leased Out"
|
|
|
|
updateSceneConnections()
|
|
|
|
|
|
|
|
return dials[i].Connection, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func releaseConnection(conn *net.TCPConn) {
|
|
|
|
for i := range dials {
|
|
|
|
if dials[i].Connection.LocalAddr().String() != conn.LocalAddr().String() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
dials[i].Status = "Connected"
|
|
|
|
updateSceneConnections()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if connection is still up by reading the heartbeat data from the server
|
2023-02-27 06:04:26 +00:00
|
|
|
// returns err if connection is not active
|
2023-02-28 06:33:35 +00:00
|
|
|
func isConnUp(conn *net.TCPConn) bool {
|
2023-02-27 06:04:26 +00:00
|
|
|
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
|
|
|
|
|
|
|
|
buf := make([]byte, 128)
|
|
|
|
_, err := conn.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("connection error detected: %v", err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var zero time.Time
|
|
|
|
conn.SetReadDeadline(zero)
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|