Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
46 lines
1.2 KiB
Go
46 lines
1.2 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
)
|
|
|
|
const defaultMaxDeadlockRetries = 3
|
|
|
|
// WithDeadlockRetry retries fn when MySQL returns Error 1213 (deadlock).
|
|
// Retries up to 3 times with exponential backoff (50ms, 100ms, 200ms).
|
|
// Safe for all DB drivers — non-deadlock errors are returned immediately.
|
|
func WithDeadlockRetry(ctx context.Context, fn func() error) error {
|
|
var err error
|
|
for attempt := 0; attempt <= defaultMaxDeadlockRetries; attempt++ {
|
|
err = fn()
|
|
if err == nil || !IsDeadlockError(err) {
|
|
return err
|
|
}
|
|
if attempt < defaultMaxDeadlockRetries {
|
|
wait := time.Duration(50<<attempt) * time.Millisecond
|
|
logger.Warnf(ctx, "[DeadlockRetry] attempt %d/%d failed with deadlock, retrying in %v: %v",
|
|
attempt+1, defaultMaxDeadlockRetries, wait, err)
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(wait):
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// IsDeadlockError checks whether err is a MySQL deadlock (Error 1213).
|
|
func IsDeadlockError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
msg := err.Error()
|
|
return strings.Contains(msg, "Error 1213") ||
|
|
strings.Contains(msg, "Deadlock found when trying to get lock")
|
|
}
|