mymmrac / telego

Telegram Bot API library for Go
https://telego.pixelbox.dev
MIT License
542 stars 42 forks source link

Memory leaks when calling SendMediaGroup #199

Closed Spargwy closed 1 month ago

Spargwy commented 3 months ago

💬 Telego version

v0.31.0

👾 Issue description

Calling SendMediaGroup with image but deeper I have a strange growth of bytes buffer

⚡️ Expected behavior

just send media

🧐 Code example

package main

import (
    "bytes"
    "fmt"
    "log"
    "os"

    "github.com/mymmrac/telego"
)

type ByteNamedReader struct {
    data []byte
    name string
}

func (b *ByteNamedReader) Name() string {
    return b.name
}

func (b *ByteNamedReader) Read(p []byte) (n int, err error) {
    return bytes.NewReader(b.data).Read(p)
}

func main() {
    // Get Bot token from environment variables
    botToken := "token"

    // Note: Please keep in mind that default logger may expose sensitive information,
    // use in development only
    bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    // Get updates channel
    // (more on configuration in examples/updates_long_polling/main.go)
    updates, _ := bot.UpdatesViaLongPolling(nil)

    // Stop reviving updates from update channel
    defer bot.StopLongPolling()

    f, err := os.Open("proxy-image.jpg")
    if err != nil {
        panic(err)
    }

    media := []telego.InputMedia{}

    stat, err := f.Stat()
    if err != nil {
        panic(err)
    }

    fc := make([]byte, stat.Size())
    _, err = f.Read(fc)
    if err != nil {
        panic(err)
    }

    namedReader := &ByteNamedReader{
        data: fc,
        name: "proxy-image.jpg",
    }

    log.Print(len(fc))

    media = append(media, &telego.InputMediaPhoto{
        Media: telego.InputFile{File: namedReader},
    })

    // Loop through all updates when they came
    for update := range updates {
        _, err = bot.SendMediaGroup(&telego.SendMediaGroupParams{ChatID: update.Message.Chat.ChatID(), Media: media})
        if err != nil {
            panic(err)
        }
    }
}
NikBuka commented 2 months ago

@mymmrac hi, we have this problem too, help us please

NikBuka commented 1 month ago

@mymmrac hey, do you have any information about this problem?

mymmrac commented 1 month ago

@NikBuka I spend some time investigating this, but from my side, don't see any particular issues, there is io.Copy that allocates some memory, but then it's written to a buffer and writer is closed, so I don't see places where memory leaks, I couldn't reproduce it fully, can you please provide full code example + files you are using + memory profile to see what exactly is happening for you?

Spargwy commented 1 month ago

@mymmrac Hi. Code example that I provided is fully working. I use pic with size about 18kB(attachment). Just run the bot and start chatting. Really, the problem does not recur? proxy-image

mymmrac commented 1 month ago

@Spargwy there are couple of issues with your code:

  1. ByteNamedReader is incorrect implementation of the reader, it may not return EOF if read buffer is smaller then file size which is required
  2. You can't reuse readers, for each file you need to have new reader
  3. You have memory leack because none of the updates were processes actually, Read method always returned some data and no error (no EOF) and it kept reading infinite data

Simply create new InputFile for each message using tu.NameReader and bytes.NewReader:

for update := range updates {
    media := []telego.InputMedia{&telego.InputMediaPhoto{
        Type:  telego.MediaTypePhoto,
        Media: telego.InputFile{File: tu.NameReader(bytes.NewReader(fc), "proxy-image.jpg")},
    }}
    _, err = bot.SendMediaGroup(&telego.SendMediaGroupParams{ChatID: update.Message.Chat.ChatID(), Media: media})
    if err != nil {
        panic(err)
    }
}