Reputation: 21
I'm developing a Go application that integrates with MongoDB to create, read, and delete images. I've set the connection pool to 4 for testing purposes, but I've noticed that the number of connections can go up to 10 even if I haven't hit any endpoint related to MongoDB. If I don't hit any endpoint, the number of connections stays between 2-3. There's a connection leak in my code, and I'm having trouble figuring out how to release resources correctly to ensure that they are returned to the pool.
As the documentation says mongo-go driver is goroutine safe so I have a function that initialize mongo and set a global variable with the initialized mongo client
package persistence
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
var MongoClient *mongo.Client
func InitMongo(ctx context.Context, URI string) error {
if MongoClient != nil {
return nil
}
serverAPI := options.ServerAPI(options.ServerAPIVersion1)
opts := options.Client().ApplyURI(URI).SetServerAPIOptions(serverAPI)
opts.SetMinPoolSize(2)
opts.SetMaxPoolSize(4)
opts.SetMaxConnIdleTime(2 * time.Second)
// Create a new client and connect to the server
client, err := mongo.Connect(ctx, opts)
if err != nil {
return err
}
MongoClient = client
// Send a ping to confirm a successful connection
var result bson.M
if err = client.Database("admin").RunCommand(context.TODO(), bson.D{{"ping", 1}}).Decode(&result); err != nil {
return err
}
return nil
}
and I use this function in the main to initialize it
func main() {
configuration := config.NewEnvConfigs()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = persistence.InitMongo(ctx, configuration.MongoDBURI)
if err != nil {
fmt.Println("Mongo not ready")
panic(err)
}
fmt.Println("mongo connected")
defer func() {
if err = persistence.MongoClient.Disconnect(context.TODO()); err != nil {
panic(err)
}
}()
r := server.Routes()
http.ListenAndServe("0.0.0.0:3333", r)
}
also the last piece of code that uses the mongo client is the handlers for images endpoints
package handlers
import (
"context"
"encoding/base64"
"errors"
"fmt"
"github.com/bycultivaet/backend/internal/infrastructure/persistence"
"github.com/go-chi/chi"
"github.com/go-chi/render"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"io/ioutil"
"mime/multipart"
"net/http"
)
func GetImageByID() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
Uuid := chi.URLParam(r, "uuid")
_, err := uuid.Parse(Uuid)
if err != nil {
render.Status(r, 400)
render.Respond(w, r, errors.New("invalid uuid").Error())
return
}
filter := bson.M{"uuid": Uuid}
var result bson.M
collection := persistence.MongoClient.Database("hassad-media").Collection("images")
err = collection.FindOne(ctx, filter).Decode(&result)
if err != nil {
render.Status(r, 404)
render.Respond(w, r, errors.New("image not found").Error())
return
}
imageData, ok := result["image"].(primitive.Binary)
if !ok {
render.Status(r, 500)
render.Respond(w, r, errors.New("error parsing image").Error())
return
}
imageBase64 := base64.StdEncoding.EncodeToString(imageData.Data)
render.Status(r, 200)
render.JSON(w, r, map[string]string{"image": imageBase64})
}
}
func UploadImage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
cancelled := r.Context().Done()
select {
case <-cancelled:
render.Status(r, 499)
render.Respond(w, r, errors.New("request cancelled").Error())
return
default:
err := r.ParseMultipartForm(10 << 20)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.Respond(w, r, err.Error())
return
}
file, _, err := r.FormFile("image")
if err != nil {
render.Status(r, http.StatusBadRequest)
render.Respond(w, r, err.Error())
return
}
defer file.Close()
Uuid, err := uuid.NewRandom()
if err != nil {
render.Status(r, http.StatusInternalServerError)
render.Respond(w, r, errors.New("error generating uuid").Error())
return
}
// Pass the contents of the file to GetMongoDB
_, err = UploadPhoto(file, Uuid.String(), ctx)
if err != nil {
render.Status(r, http.StatusInternalServerError)
render.Respond(w, r, err.Error())
return
}
render.Status(r, http.StatusOK)
render.JSON(w, r, map[string]string{"uuid": Uuid.String()})
}
}
}
func UploadPhoto(file multipart.File, uuid string, ctx context.Context) (interface{}, error) {
imageBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
imageDoc := bson.M{"image": imageBytes, "uuid": uuid}
defer file.Close()
collection := persistence.MongoClient.Database("hassad-media").Collection("images")
data, err := collection.InsertOne(ctx, imageDoc)
if err != nil {
return 0, err
}
fmt.Println("Inserted image into MongoDB!")
fmt.Println(data.InsertedID)
return data.InsertedID, nil
}
func DeleteImageByID() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
uuid := chi.URLParam(r, "uuid")
filter := bson.M{"uuid": uuid}
collection := persistence.MongoClient.Database("hassad-media").Collection("images")
res, err := collection.DeleteOne(ctx, filter)
if err != nil {
if res.DeletedCount == 0 {
render.Status(r, 404)
render.Respond(w, r, errors.New("image not found").Error())
return
}
render.Status(r, 500)
render.Respond(w, r, err.Error())
return
}
render.JSON(w, r, map[string]string{"answer": "deleted"})
}
}
what I'm doing wrong closing the connections to be returned to the pool or any other configuration
I test my code by with python script that hits the endpoints excessively
I tried to set collection and database global instead of client it didn't work
Upvotes: 1
Views: 170