Skip to content

teff.tool.builtin.lock

teff.tool.builtin.lock

Distributed lock over a Redis-compatible store (Redis/KeyDB/Valkey).

A single lock tool with an action selector: acquire, release, renew, status. A unique token is generated per tool instance, so only the instance that acquired a lock can release or renew it — release/renew are compare-and-{del,expire} Lua scripts, atomic on the server, so a stale instance can never clobber a lock it does not own.

Useful in daemon workflows where several processes (or several ticks) must not review the same pull request at once.

Classes:

Name Description
LockTool

Distributed lock over Redis (KeyDB/Valkey supported).

LockTool

Bases: _RedisBase

Distributed lock over Redis (KeyDB/Valkey supported).

Parameters:

Name Type Description Default
action

acquire | release | renew | status.

required
key

Lock name.

required
ttl

Lease length in seconds for acquire (default 30) or the new lease for renew.

required

Args (config): same as the redis tool — url or host/port/db/password/username.

Source code in teff/tool/builtin/lock.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class LockTool(_RedisBase):
    """Distributed lock over Redis (KeyDB/Valkey supported).

    Args:
        action: ``acquire`` | ``release`` | ``renew`` | ``status``.
        key: Lock name.
        ttl: Lease length in seconds for ``acquire`` (default 30) or the
            new lease for ``renew``.

    Args (config): same as the ``redis`` tool — ``url`` or
        ``host``/``port``/``db``/``password``/``username``.
    """

    name = "lock"
    description = (
        "Distributed lock over Redis-compatible stores (acquire, release, "
        "renew, status)"
    )

    def __init__(self, config: dict | None = None):
        super().__init__(config)
        self.token = uuid.uuid4().hex

    def run(  # type: ignore[override]
        self, action: str, key: str = "", ttl: int = 30
    ) -> str:
        if not action:
            raise ValueError("action is required (acquire, release, renew, status)")
        if not key:
            raise ValueError("key is required")
        client = self._client()
        try:
            a = action.lower()
            if a == "acquire":
                if int(ttl) <= 0:
                    raise ValueError("ttl must be > 0")
                ok = client.set(key, self.token, nx=True, ex=int(ttl))
                return "acquired" if ok else "held by someone else"
            if a == "release":
                released = client.eval(_DEL_IF_MATCH, 1, key, self.token)
                return "released" if released else "not held (or owned by someone else)"
            if a == "renew":
                renewed = client.eval(_EXPIRE_IF_MATCH, 1, key, self.token, int(ttl))
                return (
                    f"renewed {key} for {ttl}s"
                    if renewed
                    else "not held (or owned by someone else)"
                )
            if a == "status":
                holder = client.get(key)
                if holder is None:
                    return f"{key} is free"
                remaining = client.ttl(key)
                who = (
                    "me"
                    if holder == self.token
                    else f"another holder ({holder[:8]}...)"
                )
                return f"{key} held by {who} ({remaining}s left)"
            raise ValueError(f"unknown action: {a}")
        finally:
            client.close()