Compare commits
No commits in common. "bface08f20b816943450046be05f87f9f26541f0" and "5d5e28ad948530a2a082a6d98cc1a05ad9cb612a" have entirely different histories.
bface08f20
...
5d5e28ad94
@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"log"
|
|
||||||
"math/bits"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is an example client program and is a placeholder for a real life situation
|
|
||||||
// In thie example, the program has a message it needs to send, and it's given a connection
|
|
||||||
// from the pool to use. At the end of the program / function it releases the connection back tot he pool
|
|
||||||
func clientProgram(message string) {
|
|
||||||
|
|
||||||
conn, err := requestLease()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("could not get connection from pool")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("attempted to send message '%s'", message)
|
|
||||||
integerMessage, err := strconv.Atoi(message)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to conver message '%s' to an integer: %v", message, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(buf, uint64(integerMessage))
|
|
||||||
_, err = conn.Write(buf[bits.LeadingZeros64(uint64(integerMessage))>>3:])
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to write to socket: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseConnection(conn)
|
|
||||||
}
|
|
132
client/main.go
132
client/main.go
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/g3n/engine/app"
|
"github.com/g3n/engine/app"
|
||||||
@ -21,17 +20,12 @@ import (
|
|||||||
"github.com/g3n/engine/window"
|
"github.com/g3n/engine/window"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
scene *core.Node
|
|
||||||
tableConnections *gui.Table
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// Create application and scene
|
// Create application and scene
|
||||||
a := app.App(1920, 1080, "Leaky Pool")
|
a := app.App(1920, 1080, "Leaky Pool")
|
||||||
|
|
||||||
scene = core.NewNode()
|
scene := core.NewNode()
|
||||||
|
|
||||||
// Set the scene to be managed by the gui manager
|
// Set the scene to be managed by the gui manager
|
||||||
gui.Manager().Set(scene)
|
gui.Manager().Set(scene)
|
||||||
@ -80,66 +74,6 @@ func main() {
|
|||||||
//GUI//
|
//GUI//
|
||||||
///////
|
///////
|
||||||
|
|
||||||
// Connections Table
|
|
||||||
var err error
|
|
||||||
tableConnections, err = gui.NewTable(200, 200, []gui.TableColumn{
|
|
||||||
{Id: "1", Header: "Local Port", Width: 75, Expand: 0},
|
|
||||||
{Id: "2", Header: "Status", Width: 125, Expand: 0},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to create tableConnections: %v", err)
|
|
||||||
}
|
|
||||||
tableConnections.SetBorders(1, 1, 1, 1)
|
|
||||||
tableConnections.SetPosition(0, 35)
|
|
||||||
tableConnections.SetMargins(10, 10, 10, 10)
|
|
||||||
|
|
||||||
editSend := gui.NewEdit(150, "data to transmit...")
|
|
||||||
editSend.SetPosition(tableConnections.Position().X+tableConnections.Width()+10, tableConnections.Position().Y+15)
|
|
||||||
scene.Add(editSend)
|
|
||||||
|
|
||||||
buttonSendRelease := gui.NewButton("Send and Release")
|
|
||||||
buttonSendRelease.SetPosition(editSend.Position().X+editSend.Width()+10, editSend.Position().Y-2)
|
|
||||||
buttonSendRelease.Subscribe(gui.OnClick, func(name string, ev interface{}) {
|
|
||||||
// Get the selected connection in the table
|
|
||||||
// Get the editSend text
|
|
||||||
// Call the "client program" function to convert to integer and transmit, then release
|
|
||||||
/* if len(tableConnections.SelectedRows()) != 1 {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
row := tableConnections.Rows(0, -1)[tableConnections.SelectedRows()[0]]
|
|
||||||
if row["2"] != "Leased Out" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var conn *net.TCPConn
|
|
||||||
|
|
||||||
for i := range dials {
|
|
||||||
if dials[i].Connection == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if row["1"] != strings.Split(dials[i].Connection.LocalAddr().String(), ":")[1] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
conn = dials[i].Connection
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if conn == nil {
|
|
||||||
log.Printf("could not match selected table entry to dialed connection")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
clientProgram(editSend.Text())
|
|
||||||
|
|
||||||
})
|
|
||||||
scene.Add(buttonSendRelease)
|
|
||||||
|
|
||||||
labelServer := gui.NewLabel("Server: ")
|
labelServer := gui.NewLabel("Server: ")
|
||||||
labelServer.SetPosition(10, 8)
|
labelServer.SetPosition(10, 8)
|
||||||
scene.Add(labelServer)
|
scene.Add(labelServer)
|
||||||
@ -158,28 +92,30 @@ func main() {
|
|||||||
editPoolSize.SetPosition(labelPoolSize.Position().X+labelPoolSize.Width()+10, 10)
|
editPoolSize.SetPosition(labelPoolSize.Position().X+labelPoolSize.Width()+10, 10)
|
||||||
scene.Add(editPoolSize)
|
scene.Add(editPoolSize)
|
||||||
|
|
||||||
buttonPoolSize := gui.NewButton("Set")
|
buttonConnect := gui.NewButton("Start Pool")
|
||||||
buttonPoolSize.SetPosition(editPoolSize.Position().X+editPoolSize.Width()+10, 8)
|
buttonConnect.SetPosition(editPoolSize.Position().X+editPoolSize.Width()+10, 8)
|
||||||
buttonPoolSize.Subscribe(gui.OnClick, func(name string, ev interface{}) {
|
buttonConnect.Subscribe(gui.OnClick, func(name string, ev interface{}) {
|
||||||
|
|
||||||
poolSize, err := strconv.Atoi(editPoolSize.Text())
|
poolSize, err := strconv.Atoi(editPoolSize.Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to convert poolSize '%d' to integer: %v", poolSize, err)
|
log.Printf("failed to convert poolSize '%d' to integer: %v", poolSize, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setConnPoolSize(poolSize)
|
go connPoolWatchdog(editServer.Text(), poolSize, scene) // goroutine that keeps the pool full of healthy TCP connections
|
||||||
})
|
|
||||||
scene.Add(buttonPoolSize)
|
|
||||||
|
|
||||||
buttonStartPool := gui.NewButton("Start Pool")
|
buttonConnect.Label.SetText("Stop Pool") // stop not implemented
|
||||||
buttonStartPool.SetPosition(buttonPoolSize.Position().X+buttonPoolSize.Width()+10, 8)
|
|
||||||
buttonStartPool.Subscribe(gui.OnClick, func(name string, ev interface{}) {
|
|
||||||
go connPoolWatchdog(editServer.Text()) // goroutine that keeps the pool full of healthy TCP connections
|
|
||||||
buttonStartPool.Label.SetText("Stop Pool") // stop not implemented - need to restart program
|
|
||||||
|
|
||||||
})
|
})
|
||||||
scene.Add(buttonStartPool)
|
scene.Add(buttonConnect)
|
||||||
|
|
||||||
scene.Add(tableConnections)
|
// Create and add a button to the scene
|
||||||
|
btn := gui.NewButton("Make Red")
|
||||||
|
btn.SetPosition(100, 40)
|
||||||
|
btn.SetSize(40, 40)
|
||||||
|
btn.Subscribe(gui.OnClick, func(name string, ev interface{}) {
|
||||||
|
mat.SetColor(math32.NewColor("DarkRed"))
|
||||||
|
})
|
||||||
|
scene.Add(btn)
|
||||||
|
|
||||||
// Run the application
|
// Run the application
|
||||||
a.Run(func(renderer *renderer.Renderer, deltaTime time.Duration) {
|
a.Run(func(renderer *renderer.Renderer, deltaTime time.Duration) {
|
||||||
@ -188,39 +124,3 @@ func main() {
|
|||||||
renderer.Render(scene, cam)
|
renderer.Render(scene, cam)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// updates the scene based on the status of the dial
|
|
||||||
// Disconnected, Connected, Leased Out
|
|
||||||
|
|
||||||
// Disconnected:
|
|
||||||
// Connected: dials[i].Mat.SetColor(math32.NewColor("LimeGreen"))
|
|
||||||
// Leased Out:
|
|
||||||
|
|
||||||
func updateSceneConnections() {
|
|
||||||
rows := make([]map[string]interface{}, 0, len(dials))
|
|
||||||
for i := range dials {
|
|
||||||
row := make(map[string]interface{})
|
|
||||||
if dials[i].Connection != nil {
|
|
||||||
row["1"] = strings.Split(dials[i].Connection.LocalAddr().String(), ":")[1]
|
|
||||||
} else {
|
|
||||||
row["1"] = "N/A"
|
|
||||||
}
|
|
||||||
row["2"] = dials[i].Status
|
|
||||||
switch dials[i].Status {
|
|
||||||
case "Disconnected":
|
|
||||||
dials[i].Mat.SetColor(math32.NewColor("Grey"))
|
|
||||||
case "Connected":
|
|
||||||
dials[i].Mat.SetColor(math32.NewColor("LimeGreen"))
|
|
||||||
|
|
||||||
case "Leased Out":
|
|
||||||
dials[i].Mat.SetColor(math32.NewColor("Blue"))
|
|
||||||
|
|
||||||
default:
|
|
||||||
log.Printf("connection has unknown status! Should be one of Disconnected, Connected or Leased Out")
|
|
||||||
}
|
|
||||||
|
|
||||||
rows = append(rows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
tableConnections.SetRows(rows)
|
|
||||||
}
|
|
||||||
|
141
client/pool.go
141
client/pool.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@ -13,53 +12,23 @@ import (
|
|||||||
"github.com/g3n/engine/math32"
|
"github.com/g3n/engine/math32"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var dials []Dial
|
||||||
dials []Dial
|
|
||||||
)
|
|
||||||
|
|
||||||
type Dial struct {
|
type Dial struct {
|
||||||
Connection *net.TCPConn // the real deal underlying TCP connection
|
Connection net.Conn // the real deal underlying TCP connection
|
||||||
Geo *geometry.Geometry // the 3D geometry
|
Geo *geometry.Geometry // the 3D geometry
|
||||||
Mat *material.Standard // the material / color
|
Mat *material.Standard // the material / color
|
||||||
Mesh *graphic.Mesh // the combined geometry and material
|
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
|
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
|
// 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
|
// should be ran in its own goroutine
|
||||||
func connPoolWatchdog(serverAddress string) {
|
func connPoolWatchdog(serverAddress string, maxPoolSize int, scene *core.Node) {
|
||||||
|
|
||||||
|
dials = make([]Dial, 0)
|
||||||
|
|
||||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress)
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err)
|
log.Fatalf("failed to resolve serverAddress '%s': %v", serverAddress, err)
|
||||||
@ -67,44 +36,48 @@ func connPoolWatchdog(serverAddress string) {
|
|||||||
|
|
||||||
for {
|
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
|
// Check status of existing open connections in pool
|
||||||
for i := range dials {
|
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]
|
||||||
|
|
||||||
// if there is no existing connection
|
// fill any empty slots in the pool with fresh connections
|
||||||
if dials[i].Connection == nil {
|
for poolSize := len(dials); poolSize < maxPoolSize; poolSize++ {
|
||||||
log.Printf("dialing new connection")
|
log.Printf("Current pool size is '%d', desired pool size is '%d' - opening new connection...", poolSize, maxPoolSize)
|
||||||
var err error
|
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||||
dials[i].Connection, err = net.DialTCP("tcp", nil, tcpAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to dial TCP connection: %v", err)
|
log.Printf("failed to dial TCP connection: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
dials[i].Connection.SetKeepAlive(true)
|
conn.SetKeepAlive(true)
|
||||||
dials[i].Connection.SetKeepAlivePeriod(time.Second * 5)
|
conn.SetKeepAlivePeriod(time.Second * 5)
|
||||||
dials[i].Status = "Connected"
|
|
||||||
stateChange = true
|
|
||||||
|
|
||||||
continue
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// test if the connection is still working
|
// Update the position of the connections on the scene
|
||||||
if !isConnUp(dials[i].Connection) { // TBD: this should be moved to check before first write to the socket file descriptor instead of polling here
|
padding := float32(200 / (len(dials) + 1)) // space between the connection objects in 3D space
|
||||||
log.Printf("closing bad idle connection and removing from pool - %s", dials[i].Connection.LocalAddr().String())
|
currX := float32(len(dials)/2) * -padding // variable gets updated, this is the initial starting position (left most connection in 3D space)
|
||||||
dials[i].Connection.Close()
|
if len(dials)%2 == 0 {
|
||||||
dials[i].Connection = nil
|
currX += padding / 2
|
||||||
dials[i].Status = "Disconnected"
|
|
||||||
stateChange = true
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
//currX := padding - 100
|
||||||
}
|
for _, dial := range dials {
|
||||||
|
dial.Mesh.SetPositionX(currX)
|
||||||
if stateChange {
|
currX += padding
|
||||||
updateSceneConnections()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Second * 5) // random sleep, AKA evidence this shouldn't be a watchdog,
|
time.Sleep(time.Second * 5) // random sleep, AKA evidence this shouldn't be a watchdog,
|
||||||
@ -112,45 +85,9 @@ func connPoolWatchdog(serverAddress string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TBD: Refactor to buffered channel instead of finding one on the fly?
|
// Checks with OS to ensure that a connection is still active
|
||||||
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
|
// returns err if connection is not active
|
||||||
func isConnUp(conn *net.TCPConn) bool {
|
func isConnUp(conn net.Conn) bool {
|
||||||
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
|
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
|
||||||
|
|
||||||
buf := make([]byte, 128)
|
buf := make([]byte, 128)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user