| 56 | } |
| 57 | |
| 58 | func (l *PostgresLock) TryAcquire(ctx context.Context, key string) (bool, func(), error) { |
| 59 | intKey := hashKey(key) |
| 60 | |
| 61 | conn, err := l.db.Conn(ctx) |
| 62 | if err != nil { |
| 63 | return false, nil, fmt.Errorf("acquiring DB connection: %w", err) |
| 64 | } |
| 65 | |
| 66 | var acquired bool |
| 67 | if err := conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock($1)", intKey).Scan(&acquired); err != nil { |
| 68 | _ = conn.Close() |
| 69 | return false, nil, fmt.Errorf("pg_try_advisory_lock: %w", err) |
| 70 | } |
| 71 | |
| 72 | if !acquired { |
| 73 | _ = conn.Close() |
| 74 | return false, nil, nil |
| 75 | } |
| 76 | |
| 77 | release := func() { |
| 78 | // pg_advisory_unlock must run on the same session that took the lock, |
| 79 | // and must run even if the caller's context was cancelled (e.g. shutdown). |
| 80 | // Bounded so a stuck session can't hang the release path. |
| 81 | releaseCtx, cancel := context.WithTimeout(context.Background(), advisoryUnlockTimeout) |
| 82 | defer cancel() |
| 83 | if _, err := conn.ExecContext(releaseCtx, "SELECT pg_advisory_unlock($1)", intKey); err != nil { |
| 84 | l.log.Warnw("msg", "failed to release advisory lock", "key", key, "error", err) |
| 85 | } |
| 86 | if err := conn.Close(); err != nil { |
| 87 | l.log.Warnw("msg", "failed to return DB connection to pool", "key", key, "error", err) |
| 88 | } |
| 89 | } |
| 90 | return true, release, nil |
| 91 | } |
| 92 | |
| 93 | // hashKey turns an opaque string key into the int64 that |
| 94 | // pg_advisory_lock expects. FNV-1a gives a stable, well-distributed |