package main import ( "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.Conn // 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 } // 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, maxPoolSize int, scene *core.Node) { dials = make([]Dial, 0) tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress) if err != nil { log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err) } for { // Check status of existing open connections in pool i := 0 // output index for _, dial := range dials { if !isConnUp(dial.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", dial.Connection.LocalAddr().String()) dial.Connection.Close() scene.Remove(dial.Mesh) continue } dials[i] = dial i++ } dials = dials[:i] // fill any empty slots in the pool with fresh connections for poolSize := len(dials); poolSize < maxPoolSize; poolSize++ { log.Printf("Current pool size is '%d', desired pool size is '%d' - opening new connection...", poolSize, maxPoolSize) conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { log.Printf("failed to dial TCP connection: %v", err) break } conn.SetKeepAlive(true) conn.SetKeepAlivePeriod(time.Second * 5) dial := Dial{Connection: conn, Geo: geometry.NewBox(1, 1, 1), Mat: material.NewStandard(math32.NewColor("LimeGreen"))} dial.Mesh = graphic.NewMesh(dial.Geo, dial.Mat) dial.Node = scene.Add(dial.Mesh) dials = append(dials, dial) } // Update the position of the connections on the scene padding := float32(200 / (len(dials) + 1)) // space between the connection objects in 3D space currX := float32(len(dials)/2) * -padding // variable gets updated, this is the initial starting position (left most connection in 3D space) if len(dials)%2 == 0 { currX += padding / 2 } //currX := padding - 100 for _, dial := range dials { dial.Mesh.SetPositionX(currX) currX += padding } 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 } } // Checks with OS to ensure that a connection is still active // returns err if connection is not active func isConnUp(conn net.Conn) 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 }