HarperFast / HarperFast/harper

Record locks: delegation caps are per table, so the process-wide bound is unbounded in table count

Open
#2,581 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

`resources/recordLockCoordinator.ts` bounds what a key's home will retain:

```ts
const MAX_DELEGATIONS_PER_TABLE = 10_000;
const MAX_DELEGATIONS_PER_REQUESTER = 2_000;
```

Both are enforced inside one `LockCoordinator`, and there is **one coordinator per table**. So the
process-wide exposure is `10,000 × (tables under lock pressure)`, and a single peer's is
`2,000 × (tables)`. Neither number is bounded by anything the code controls.

This is not a leak — every grant expires on its own deadline, and the caps do refuse new grants once a
table is full. It is that the bound the constants imply is not the bound the process has.

### Why it isn't just a bigger constant

A home may never forget a delegation before its expiry (`docs/record-lock-ownership.md` §8) — that
asymmetry is the safety rule that stops a home re-granting a key its delegate still believes it holds.
So the only lever is refusing to issue more, and the refusal has to be made against a total the
process actually knows.

### What to do

Move the accounting to module level: a total grant count and a per-requester map shared by every
coordinator, decremented in `#clearGrant` and in `close()` (but **not** in `handOffTo`, which moves
grants between coordinators without changing the total).

The reason this was not done in harper#2498: module-level counters are shared across tests in one
mocha process, so `unitTests/resources/recordLockCoordinator.test.js` needs each test's coordinators
closed — or a reset seam — before the two capacity tests stop influencing each other. That is a
mechanical change, but it is test-infrastructure work that did not belong in the PR that introduced
the split.

### Sizing

The constants themselves should be set from the enablement measurements (harper-pro#824) rather than
kept at today's guesses: the right process-wide total is a function of the observed `lock()` rate and
the memory a retained `HomeGrant` costs.

### Acceptance

- The cap refuses at a process-wide total, demonstrated across two coordinators for different tables.
- A per-requester cap that one peer cannot exceed by spreading load over tables.
- `handOffTo` across a transport swap does not change the total.
- `close()` of a coordinator that was not handed off returns its budget.

Contributor guide

Open the contributing guide

Research direction

Start with resources/recordLockCoordinator.ts and docs/record-lock-ownership.md §8, then run unitTests/resources/recordLockCoordinator.test.js. Check how grants are cleared, handed off, and closed across coordinators, and update the tests so their coordinators do not share capacity state. Done means the listed process-wide, per-requester, handoff, and close() acceptance cases pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.