initial commit

This commit is contained in:
Steven Polley 2023-02-26 23:04:26 -07:00
parent 8024e29835
commit 7d94b0e9c2
11 changed files with 325 additions and 1 deletions

View File

@ -1,3 +1,3 @@
# leaky-pool # leaky-pool
A connection pool that leaks on purpose A working connection pool that leaks on purpose for demonstration purposes.

2
client/README.MD Normal file
View File

@ -0,0 +1,2 @@
# leaky-pool client

BIN
client/client.exe Normal file

Binary file not shown.

12
client/go.mod Normal file
View File

@ -0,0 +1,12 @@
module deadbeef.codes/steven/leaky-pool/client
go 1.20
require github.com/g3n/engine v0.2.0
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

14
client/go.sum Normal file
View File

@ -0,0 +1,14 @@
github.com/g3n/engine v0.2.0 h1:7dmj4c+3xHcBnYrVmRuVf/oZ2JycxJU9Y+2FQj1Af2Y=
github.com/g3n/engine v0.2.0/go.mod h1:rnj8jiLdKEDI8VbveKhmdL4rovjjy+uxNP5YROg2x8g=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

126
client/main.go Normal file
View File

@ -0,0 +1,126 @@
package main
import (
"log"
"strconv"
"time"
"github.com/g3n/engine/app"
"github.com/g3n/engine/camera"
"github.com/g3n/engine/core"
"github.com/g3n/engine/geometry"
"github.com/g3n/engine/gls"
"github.com/g3n/engine/graphic"
"github.com/g3n/engine/gui"
"github.com/g3n/engine/light"
"github.com/g3n/engine/material"
"github.com/g3n/engine/math32"
"github.com/g3n/engine/renderer"
"github.com/g3n/engine/util/helper"
"github.com/g3n/engine/window"
)
func main() {
// Create application and scene
a := app.App(1920, 1080, "Leaky Pool")
scene := core.NewNode()
// Set the scene to be managed by the gui manager
gui.Manager().Set(scene)
// Create perspective camera
cam := camera.New(1)
cam.SetPosition(0, 0, 80)
scene.Add(cam)
// Set up orbit control for the camera
camera.NewOrbitControl(cam)
// Set up callback to update viewport and camera aspect ratio when the window is resized
onResize := func(evname string, ev interface{}) {
// Get framebuffer size and update viewport accordingly
width, height := a.GetSize()
a.Gls().Viewport(0, 0, int32(width), int32(height))
// Update the camera's aspect ratio
cam.SetAspect(float32(width) / float32(height))
}
a.Subscribe(window.OnWindowSize, onResize)
onResize("", nil)
// Create a blue torus and add it to the scene
geom := geometry.NewTorus(1, .4, 12, 32, math32.Pi*2)
mat := material.NewStandard(math32.NewColor("DarkBlue"))
mesh := graphic.NewMesh(geom, mat)
scene.Add(mesh)
// Create and add lights to the scene
scene.Add(light.NewAmbient(&math32.Color{1.0, 1.0, 1.0}, 0.8))
pointLight := light.NewPoint(&math32.Color{1, 1, 1}, 5.0)
pointLight.SetPosition(1, 0, 2)
scene.Add(pointLight)
// Create and add an axis helper to the scene
scene.Add(helper.NewAxes(0.5))
// Set background color to gray
//a.Gls().ClearColor(0.5, 0.5, 0.5, 1.0)
a.Gls().ClearColor(0, 0, 0, 1.0)
///////
//GUI//
///////
labelServer := gui.NewLabel("Server: ")
labelServer.SetPosition(10, 8)
scene.Add(labelServer)
editServer := gui.NewEdit(150, "10.69.71.106:6699")
editServer.SetText("10.69.71.106:6699")
editServer.SetPosition(labelServer.Position().X+labelServer.Width()+10, 10)
scene.Add(editServer)
labelPoolSize := gui.NewLabel("Pool Size: ")
labelPoolSize.SetPosition(editServer.Position().X+editServer.Width()+10, 8)
scene.Add(labelPoolSize)
editPoolSize := gui.NewEdit(30, "10")
editPoolSize.SetText("10")
editPoolSize.SetPosition(labelPoolSize.Position().X+labelPoolSize.Width()+10, 10)
scene.Add(editPoolSize)
buttonConnect := gui.NewButton("Start Pool")
buttonConnect.SetPosition(editPoolSize.Position().X+editPoolSize.Width()+10, 8)
buttonConnect.Subscribe(gui.OnClick, func(name string, ev interface{}) {
poolSize, err := strconv.Atoi(editPoolSize.Text())
if err != nil {
log.Printf("failed to convert poolSize '%d' to integer: %v", poolSize, err)
return
}
go connPoolWatchdog(editServer.Text(), poolSize, scene) // goroutine that keeps the pool full of healthy TCP connections
buttonConnect.Label.SetText("Stop Pool") // stop not implemented
})
scene.Add(buttonConnect)
// 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
a.Run(func(renderer *renderer.Renderer, deltaTime time.Duration) {
a.Gls().Clear(gls.DEPTH_BUFFER_BIT | gls.STENCIL_BUFFER_BIT | gls.COLOR_BUFFER_BIT)
renderer.Render(scene, cam)
})
}

104
client/pool.go Normal file
View File

@ -0,0 +1,104 @@
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 {
removed := 0
// Check status of existing open connections in pool
for i, 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()
removeCompleted := scene.Remove(dial.Mesh)
log.Printf("removed: %v", removeCompleted)
dials = append(dials[:i-removed], dials[i-removed+1:]...)
removed++
}
}
// 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
}

6
go.work Normal file
View File

@ -0,0 +1,6 @@
go 1.20
use (
./client
./server
)

4
server/README.MD Normal file
View File

@ -0,0 +1,4 @@
# leaky-pool server
Listens on port 8080 for new connections, accepts them and then closes them after one minute.

3
server/go.mod Normal file
View File

@ -0,0 +1,3 @@
module deadbeef.codes/steven/leaky-pool/server
go 1.20

53
server/main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"log"
"net"
"time"
)
func main() {
listenerAddr, err := net.ResolveTCPAddr("tcp4", ":6699")
if err != nil {
log.Fatalf("could not resolve listenAddr: %v", err)
}
listener, err := net.ListenTCP("tcp", listenerAddr)
if err != nil {
log.Fatalf("could not listen on address '%s': %v", listenerAddr.String(), err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("failed to accept new incoming connection: %v", err) // if logging facility has latency this will block
continue
}
go connHandler(conn) // branch off into goroutine so we're not blocking
}
}
func connHandler(conn net.Conn) {
log.Printf("accepted new connection from: %s", conn.RemoteAddr().String())
conn.SetReadDeadline(time.Now().Add(time.Second))
for {
buf := make([]byte, 128)
bytesRead, err := conn.Read(buf)
if err == nil && bytesRead > 0 {
log.Printf("Incoming message over socket '%s': %s", conn.RemoteAddr().String(), string(buf))
}
_, err = conn.Write([]byte("1"))
if err != nil {
log.Printf("failed writing to connection '%s': %v", conn.RemoteAddr().String(), err)
conn.Close()
break
}
time.Sleep(time.Second)
}
}