DEV Community

Casey Li
Casey Li

Posted on

Keep Lease Renewal Off the Free Host

A free model may explain a stuck lock. A free server may store the redacted trace. Neither may mint a fencing token, extend a lease, or declare that a worker still owns the resource. Renewal is a clock decision, and the clock has to share a crash domain with the writer.

Double writes rarely look dramatic in the log. Two workers each received a late yes, each appended a batch, and each recorded success. The free host sounded sure. The scratch server was already up for a demo, so the renewal helper landed there by habit. Habit is not a fence.

A delayed answer from a host the worker does not control is just another packet. Packets do not own rows. Stronger language models do not change that fact. A clearer sentence about a lost lease is still a lost lease.

This field guide draws the boundary in the other direction. The process that can crash with the worker keeps the token, the expiry, and the admit bit. A remote assistant sees a redacted snapshot only after that bit is settled. Operators who want a scratch reader for those snapshots can use MonkeyCode's free model access and free server option for narration, not for the lease table. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Those two availability claims are operator-supplied. This article does not state model names, token quotas, hardware sizes, durations, or permanence, because those details were not verified from a primary source for this draft.

A lease is timed permission, not a paragraph about permission. The owner records a resource key, a monotonic fencing token, and an expiry measured on a clock it trusts. A contender may request renewal. Admission happens only when the token matches and that same clock still shows time left, or when a deliberate handoff has already stored a successor token.

A free host that can be reset, shared, or slowed by someone else's experiment cannot make that call. If the worker must ask it whether a write is still legal, the design has already fail-opened. The coat-check stub is the right picture. A careful description of the coat helps the person searching the rack, and it still does not authorize a new stub.

Narration and issuance are different jobs. Only narration may sit on a convenience host. Several smells show that issuance has already drifted, and each one is worth catching in review before the next incident writes it into the postmortem.

A renewal that succeeds only after an HTTP round trip to an unadministered host is the first smell. Timeouts become a policy argument. Teams under a deadline treat timeout as still-owner, because the other reading stops the job. That generous reading is how two writers enter the same shard.

A second smell sits in the prompt. The current fencing token appears in a tool schema, and the model is asked to return whether renewal should proceed. The token has left the building. Free-tier logs and scratch disks now hold a capability, and obedience to the reply completes the delegation even when the prompt said advice only.

A third smell is operational rather than syntactic. The free server became the lease table because the demo container was already running. Scratch hosts get wiped. Shared capacity gets reused by the next exercise. A lock table that can disappear between acquire and renew is a whiteboard with a timer painted on it.

The fourth smell is the absence of a local deny. When the advisor is down and the worker writes anyway, the advisor was the lock, and the default was fail-open. Fail-open can caption a graph. It cannot own a partition.

The split worth keeping is small enough to test on a laptop. The worker admits or refuses renewal locally. A side task exports a snapshot that says whether a token exists and how many milliseconds remain. That snapshot may travel. The integer token may not.

The following module is an unexecuted reference implementation. It is not a benchmark, and it is not a claim about any hosted product. The source label is a teaching guard, not authentication.

#!/usr/bin/env python3
"""Local lease gate. Unexecuted reference; not a measured lock service."""

from __future__ import annotations

import time
from dataclasses import dataclass


class LeaseError(Exception):
    pass


@dataclass
class Lease:
    resource: str
    token: int
    expires_at: float


class LocalLeaseOwner:
    def __init__(self, now=time.monotonic):
        self._now = now
        self._leases: dict[str, Lease] = {}
        self._next = 1

    def acquire(self, resource: str, ttl_s: float) -> Lease:
        if ttl_s <= 0:
            raise LeaseError("ttl must be positive")
        now = self._now()
        current = self._leases.get(resource)
        if current and current.expires_at > now:
            raise LeaseError("resource still leased")
        lease = Lease(resource, self._next, now + ttl_s)
        self._next += 1
        self._leases[resource] = lease
        return lease

    def renew(self, resource: str, token: int, ttl_s: float, source: str) -> Lease:
        if source != "local-worker":
            raise LeaseError("refuse renewal from %s" % source)
        if ttl_s <= 0:
            raise LeaseError("ttl must be positive")
        now = self._now()
        current = self._leases.get(resource)
        lost = (
            current is None
            or current.token != token
            or current.expires_at <= now
        )
        if lost:
            raise LeaseError("lost lease; do not write")
        renewed = Lease(resource, token, now + ttl_s)
        self._leases[resource] = renewed
        return renewed

    def redact_for_narration(self, resource: str) -> dict:
        current = self._leases.get(resource)
        if current is None:
            return {
                "resource": resource,
                "state": "absent",
                "token_present": False,
            }
        remaining = current.expires_at - self._now()
        state = "held" if remaining > 0 else "expired"
        return {
            "resource": resource,
            "state": state,
            "remaining_ms": max(0, int(remaining * 1000)),
            "token_present": True,
        }


def _demo() -> None:
    clock = {"t": 0.0}
    owner = LocalLeaseOwner(now=lambda: clock["t"])
    lease = owner.acquire("invoice-batch-7", 5.0)
    owner.renew("invoice-batch-7", lease.token, 5.0, "local-worker")
    try:
        owner.renew("invoice-batch-7", lease.token, 5.0, "free-host")
    except LeaseError as exc:
        assert "refuse renewal" in str(exc)
    else:
        raise SystemExit("remote renewal must fail")
    trace = owner.redact_for_narration("invoice-batch-7")
    assert trace["token_present"] is True
    assert "token" not in trace
    print("local renewal admitted; remote renewal refused; trace redacted")


if __name__ == "__main__":
    _demo()
Enter fullscreen mode Exit fullscreen mode

A local check is enough to pin the boundary. It does not prove cluster safety. It proves the reference refuses the wrong source and withholds the capability from the narration payload.

python3 lease_gate.py
Enter fullscreen mode Exit fullscreen mode

An expiry check belongs beside that smoke test. Advance a fake clock past the deadline and require the local source to fail closed too. A gate that only distrusts remote names, while renewing stale local tokens, is half a fence.

python3 - << 'PY'
import lease_gate as gate
clock = {"t": 0.0}
owner = gate.LocalLeaseOwner(now=lambda: clock["t"])
lease = owner.acquire("shard-a", 1.0)
clock["t"] = 1.1
try:
    owner.renew("shard-a", lease.token, 1.0, "local-worker")
except gate.LeaseError as exc:
    print("expired renew denied:", exc)
else:
    raise SystemExit("expired lease must not renew")
PY
Enter fullscreen mode Exit fullscreen mode

Reviewers can also search the worker tree for renewal calls that do not stay on the local owner. A hit is a defect, not a feature flag. An attacker-controlled label is not a trust boundary. The real boundary is a missing network method.

grep -n "renew(" worker.py | grep -v "local_owner.renew"
grep -R "fencing_token" logs/ && echo "token leaked into logs"
Enter fullscreen mode Exit fullscreen mode

Better homes exist for a real lock, and they are dull on purpose. A primary database row updated with compare-and-swap on the resource and the token keeps the fence next to the data. A consensus service the team already runs is appropriate when more than one machine must agree. A partitioned queue that never assigns the same key to two workers removes the shared lease entirely.

Each of those options fails in its own way. None of them fails because a demo server was recycled. The compare-and-swap itself can be stated as a single guarded update, with the clock read on the primary rather than supplied by a narrator.

UPDATE leases
SET expires_at = now() + interval '5 seconds'
WHERE resource = $1
  AND token = $2
  AND expires_at > now();
Enter fullscreen mode Exit fullscreen mode

Zero rows means the worker has lost the lease and must not write. The statement does not consult a model. It does not consult a scratch server. If now() on the primary and the worker clock disagree beyond the safety margin, the team fixes the clocks. It does not average them inside a prompt.

Explanation still has a place. This is where a free model earns its keep without touching the bit. Feed it the redacted snapshot. Ask for a timeline of acquire, last local renew, and expiry, written as sentences a new on-call can read. Ask which log line contradicts the worker's belief that it still holds the shard, then stop.

A sentence that recommends a write is a comment in a ticket, not an input to the gate. Paste the snapshot. Do not paste the token in order to make the prose richer. Richer prose is not worth a leaked capability.

A free server can hold that snapshot overnight: a file, a notebook, a static note for the reader. It should not hold the token table, the worker clock, or a cron that calls renew. Scratch space is for artifacts a human can delete without paging the data owner. Lease state is not one of those artifacts.

The operational picture is easy to say and easy to violate. The worker acquires and renews against the local owner or the primary store. Every renewal is a compare-and-swap. A side process exports the redacted snapshot on a timer.

If the side process dies, renewals continue. If the free host dies, renewals continue. If the primary store dies, writers stop. That last clause is the availability trade. Moving it into a helpful model call does not delete it.

Leave the free path, and do not return, when any one of these is true. The write is externally visible, such as a payment capture, a customer message, a DNS change, or a migration commit. The cost of a double write exceeds the cost of a delayed job. Two workers can hold the same key during a rolling deploy.

Nobody can name the clock that decides expiry. The reset, sleep, or reuse policy of the free server is unknown. One hit is enough to keep renewal local. Several hits mean the design was never a prototype. It was an unowned lock with a notebook attached.

Exit is finished only when three quiet checks pass, outside an incident. A remote source cannot renew, and deleting the guard fails the smoke test in the refusing direction. Application logs or a packet capture show no fencing token traveling toward the free host. Killing the free server does not change renew latency, because that server was never on the renew path.

Until those three are true, the narration hook is a second lock with a polite voice. Do not declare the integration done because the demo transcript read well.

Some teams should not use this split at all. Multi-region mutual exclusion needs a protocol that already defines partitions, fencing, and membership. The sample above is one process and one dict. It will not survive a second machine, and dressing the dict up with a remote narrator does not add a region.

People measuring model accuracy should not cite the script as a score. It never calls a model. Operators who want a model to run the cluster should not begin with lease renewal. Begin with read-only comments on redacted logs, and leave write tools unplugged.

Teams that cannot rotate the token store should not export traces, redacted or otherwise, until that store is local and boring. Free model access is the wrong tool when the prompt is only useful if it contains secrets, customer payloads, or the token itself. The fix is to delete fields until the prompt is harmless, not to hunt a larger window.

A free server is the wrong disk when the data must survive a reset, meet a retention rule, or stay inside a network boundary the free option does not claim. An unknown boundary is a reason to stay home. Unknown quotas are the same kind of reason. A draft that cannot cite a current primary source for a numeric limit should not invent one in order to sound specific.

This guide does not score MonkeyCode uptime, answer quality, or numeric limits. It does not claim the sample ran in CI. It does not improve on published lease algorithms. It only refuses to relocate their admit bit.

Laptop suspend can still jump a clock, and a production owner has to define that case before trusting a five-second TTL. Remote lock managers are legitimate when they are operated as lock managers. They are not legitimate just because a free host was idle on a Thursday.

Park redacted traces with a free reader if that helps the on-call. Keep the token on the clock that already owns the write.

Top comments (0)