initial commit
This commit is contained in:
parent
8024e29835
commit
7d94b0e9c2
@ -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
2
client/README.MD
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# leaky-pool client
|
||||||
|
|
BIN
client/client.exe
Normal file
BIN
client/client.exe
Normal file
Binary file not shown.
12
client/go.mod
Normal file
12
client/go.mod
Normal 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
14
client/go.sum
Normal 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
126
client/main.go
Normal 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
104
client/pool.go
Normal 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
|
||||||
|
}
|
4
server/README.MD
Normal file
4
server/README.MD
Normal 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
3
server/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module deadbeef.codes/steven/leaky-pool/server
|
||||||
|
|
||||||
|
go 1.20
|
53
server/main.go
Normal file
53
server/main.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user