package allocate import ( "context" "strings" "sync" "time" "github.com/gogf/gf/v2/frame/g" ) const ( defaultAllocateDeadlockMaxRetries = 3 defaultAllocateDeadlockRetryDelay = 200 * time.Millisecond ) type allocateParentLock struct { mu sync.Mutex refs int } var allocateParentLocks = struct { mu sync.Mutex values map[string]*allocateParentLock }{values: make(map[string]*allocateParentLock)} func acquireAllocateParentLock(batchNo string) func() { allocateParentLocks.mu.Lock() lock := allocateParentLocks.values[batchNo] if lock == nil { lock = &allocateParentLock{} allocateParentLocks.values[batchNo] = lock } lock.refs++ allocateParentLocks.mu.Unlock() lock.mu.Lock() return func() { lock.mu.Unlock() allocateParentLocks.mu.Lock() lock.refs-- if lock.refs == 0 { delete(allocateParentLocks.values, batchNo) } allocateParentLocks.mu.Unlock() } } func allocateDeadlockMaxRetries(ctx context.Context) int { retries := g.Cfg().MustGet(ctx, "batch.allocate.deadlockMaxRetries", defaultAllocateDeadlockMaxRetries).Int() if retries < 0 { return 0 } return retries } func allocateDeadlockRetryDelay(ctx context.Context, attempt int) time.Duration { delay := g.Cfg().MustGet(ctx, "batch.allocate.deadlockRetryDelay", defaultAllocateDeadlockRetryDelay).Duration() if delay <= 0 { delay = defaultAllocateDeadlockRetryDelay } for i := 0; i < attempt; i++ { if delay >= time.Second { return time.Second } delay *= 2 } return delay } func waitAllocateDeadlockRetry(ctx context.Context, attempt int) error { timer := time.NewTimer(allocateDeadlockRetryDelay(ctx, attempt)) defer timer.Stop() select { case <-ctx.Done(): return ctx.Err() case <-timer.C: return nil } } func isAllocateDeadlock(err error) bool { if err == nil { return false } message := strings.ToLower(err.Error()) return strings.Contains(message, "deadlock found") || strings.Contains(message, "error 1213") || strings.Contains(message, "1213 (40001)") }