sorry if the Title is misleadling I didn't know how to ask it properly. I'll explain my situation as much as I can.
I have a go-gin web server running. Lately it get's stuck. Not responding to requests anymore and I don't have a timeout logic(which might be my problem) therefore I'm waiting the request response forever. The machine is windows, I was checking resource monitor and task manager in case of leak, which was not the case. Once the web server got frozen(not responding to requests anymore) I used netstat command to see the open connections and found out there was tons of :3306 in state of TIME_WAIT
like 80ish or something. I think this is causing my web server to not respond new request's anymore. When I checked console output I saw the requests that can come in was actually getting response in 2-40ish ms. which is normal.
TL;DR
I think my database connection is not correct, it's not getting closed properly and causing to run out of ports.
I'll share my code below, can someone point me which part might be causing this issue?
//main.go
func createDBPool(connectionString string, maxConnections int) (*sql.DB, error) {
db, err := sql.Open("mysql", connectionString)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(maxConnections) // Set the maximum number of open connections
return db, nil
}
func main() {
// Initialize database connection => field GlobalDBPool *sql.DB
//config.GlobalDbCon => "dbusername:dbuserpassword@tcp(dbip:dbport)/databasename
config.GlobalDBPool, err = createDBPool(config.GlobalDbCon, 30)
if err != nil {
sC.Logger.Error("Couldn't initialize database connection to global database.", "Error", err)
os.Exit(1)
}
defer config.GlobalDBPool.Close()
//..Some other initialization codes are here
//This is how I distribute my config which contains database connection and other stuff
RSSServices := sC.RSSServices{ServerConfig: config}
userService := sC.UserService{ServerConfig: config}
subService := sC.SubscriptionService{ServerConfig: config}
//Again some code here and then routes come in place
r.GET("/some-endpoint", userService.SomeEndPointGET())
r.POST("/another-endpoint", subService.AnotherEndpointPOST())
err = r.Run(":80")
if err != nil {
sC.Logger.Error("Server couldn't be initialized.", "Error", err)
os.Exit(3)
}
}
This is how I use the connection. I've checked all my functions and they are all same/similar
//SubscriptionService.go
type UserService struct {
ServerConfig *models.Config
}
func (us *UserService) AnotherEndpointPOST() func(c *gin.Context) {
sqlStatement := `PLACEHOLDER-SQL-STATEMENT-FOR-PRIVACY`
err := us.ServerConfig.GlobalDBPool.QueryRow(sqlStatement, "param1").Scan(&var1, &var2, &var3)
if err != nil {
c.JSON(400, gin.H{"code": "some-error-code-string"})
return
}
outputVar := fmt.Sprintf("\\\\%s\\%s\\%s", var1, var2, var3)
c.JSON(200, gin.H{"code": "outputVar"})
}
I had to replace my sql and some variables. But in general this is how I use the database connection in every endpoint. I don't manually close or dispose anything which I think is wrong. Can someone point me if this approach is correct or should I close the connections? I use the "database/sql"
library in my code.
dbConn
indbConn.QueryRow
? How do you get it?main()
function callscreateDBPool()
and exists right away, the other things your mention (namedxxxService
) are initialized outside of the main function,and no code shows how the fields inconfig
are used in your code (do you useconfig.GlobalDBPool
? orconfig.GlobalDBCon
? or something else ? ...)rows.Close()
" - the usage example is probably a good place to start, there is also a tutorial and quite a few questions here. If close is not called (and you don't process the full result set) then a connection is leaked.