package main import ( "fmt" "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" ) var ( dials []Dial ) type Dial struct { Connection *net.TCPConn // the real deal underlying TCP connection 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 Status string // String to control what's displayed in the scene. Can be Disconnected, Connected, Leased Out } // 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 } dials = make([]Dial, 0) // 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) { tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress) if err != nil { log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err) } for { stateChange := false // set to true if there's been any state changes since the last polling // Check status of existing open connections in pool 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 continue } // 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 continue } } if stateChange { updateSceneConnections() } 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 } } // 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 // returns err if connection is not active func isConnUp(conn *net.TCPConn) bool { 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 }