REPOSITORY / ScuroNeko/Laniakea

Compare commits

DIFF REPOSITORY

Compare commits

...
1 Commit
Author SHA1 Message Date
ScuroNekoandClaude Sonnet 4.6 38309e74f6 (fix): polling loop exits on HTTP client timeout, not only on context cancel
Golang lint / lint (push) Successful in 1m40s
(fix): HTTP client timeout too close to poll timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:44:22 +03:00
3 changed files with 29 additions and 18 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## v1.0.2
### Fixed
- Fixed long-polling stopping permanently when the HTTP client's internal timeout fired. The polling loop was checking `errors.Is(err, context.DeadlineExceeded)`, which matched HTTP client timeout errors (`*url.Error` wraps `context.DeadlineExceeded`), causing the goroutine to exit as if the bot context was canceled. The check is now `ctx.Err() != nil` so only a real context cancellation stops polling.
- Fixed the HTTP client timeout (45 s) being too close to the long-poll `getUpdates` timeout (30 s default), leaving insufficient margin for connection setup and response transfer. The client timeout is now derived from the configured `PollTimeout` plus a 60-second buffer.
## v1.0.1 ## v1.0.1
### Fixed ### Fixed
+21 -16
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"sync" "sync"
"time" "time"
@@ -166,21 +167,6 @@ func NewBot[T any](opts *BotOpts) (*Bot[T], error) {
limiter := utils.NewRateLimiter() limiter := utils.NewRateLimiter()
limiter.SetGlobalRate(opts.RateLimit) limiter.SetGlobalRate(opts.RateLimit)
apiOpts := tgapi.NewAPIOpts(opts.Token).
SetAPIURL(opts.APIURL).
UseTestServer(opts.UseTestServer).
SetLimiter(limiter).
SetDropRateLimitOverflow(opts.DropRateLimitOverflow).
SetLogFormat(opts.LogFormat).
SetLogFormatter(opts.LogFormatter)
api := tgapi.NewAPI(apiOpts)
uploader := tgapi.NewUploader(api)
prefixes := opts.Prefixes
if len(prefixes) == 0 {
prefixes = []string{"/"}
}
workers := 32 workers := 32
if opts.MaxWorkers > 0 { if opts.MaxWorkers > 0 {
workers = opts.MaxWorkers workers = opts.MaxWorkers
@@ -191,6 +177,25 @@ func NewBot[T any](opts *BotOpts) (*Bot[T], error) {
pollTimeout = opts.PollTimeout pollTimeout = opts.PollTimeout
} }
// HTTP client timeout must exceed pollTimeout to avoid spurious deadline
// errors that the polling loop would misinterpret as context cancellation.
httpTimeout := time.Duration(pollTimeout)*time.Second + 60*time.Second
apiOpts := tgapi.NewAPIOpts(opts.Token).
SetAPIURL(opts.APIURL).
UseTestServer(opts.UseTestServer).
SetLimiter(limiter).
SetDropRateLimitOverflow(opts.DropRateLimitOverflow).
SetLogFormat(opts.LogFormat).
SetLogFormatter(opts.LogFormatter).
SetHTTPClient(&http.Client{Timeout: httpTimeout})
api := tgapi.NewAPI(apiOpts)
uploader := tgapi.NewUploader(api)
prefixes := opts.Prefixes
if len(prefixes) == 0 {
prefixes = []string{"/"}
}
bot := &Bot[T]{ bot := &Bot[T]{
updateOffset: 0, updateOffset: 0,
errorTemplate: "%s", errorTemplate: "%s",
@@ -472,7 +477,7 @@ func (bot *Bot[T]) RunWithContext(ctx context.Context) error {
default: default:
updates, err := bot.Updates(ctx) updates, err := bot.Updates(ctx)
if err != nil { if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { if ctx.Err() != nil {
return return
} }
retryDelay, ok := pollRetryAfterDelay(err) retryDelay, ok := pollRetryAfterDelay(err)
+2 -2
View File
@@ -2,13 +2,13 @@ package utils
const ( const (
// VersionString is the module version string. // VersionString is the module version string.
VersionString = "1.0.1" VersionString = "1.0.2"
// VersionMajor is the module major version. // VersionMajor is the module major version.
VersionMajor = 1 VersionMajor = 1
// VersionMinor is the module minor version. // VersionMinor is the module minor version.
VersionMinor = 0 VersionMinor = 0
// VersionPatch is the module patch version. // VersionPatch is the module patch version.
VersionPatch = 1 VersionPatch = 2
// VersionBeta is the prerelease counter for the current version. // VersionBeta is the prerelease counter for the current version.
VersionBeta = 0 VersionBeta = 0
) )