TinyStore

Locks / Holding and waiting

Holding and waiting

hold waits for a key, holds it while your function runs, and lets it go when the function returns or throws. Waiters stand in line, first come first served. tryHold never waits: it runs the function only if the key is free.

const invoices = store.lock('invoices', { ttl: '10s' })

await invoices.hold(invoice.id, async lease => {
	await charge(invoice, { fence: lease.fence, signal: lease.signal })
})
await invoices.hold(invoice.id, work, { wait: '30s' }) // stand in line up to 30 seconds

const tried = await invoices.tryHold(invoice.id, work)
if (tried.acquired) use(tried.value)
else say('someone is charging it already')
invoices = store.lock("invoices", ttl="10s")

async with invoices.hold(invoice.id) as lease:
    await charge(invoice, fence=lease.fence)
async with invoices.hold(invoice.id, wait="30s") as lease:
    await work(lease)

async with invoices.try_hold(invoice.id) as lease:
    if lease is None:
        say("someone is charging it already")
    else:
        use(await work(lease))
invoices, err := locks.OpenLock(store, "invoices", locks.TTL(10*time.Second))

err = invoices.Hold(ctx, invoice.ID, func(ctx context.Context, lease *locks.Lease) error {
	return charge(ctx, invoice, lease.Fence) // ctx ends when the lease is lost
})
err = invoices.Hold(ctx, invoice.ID, work, locks.Wait(30*time.Second))

acquired, err := invoices.TryHold(ctx, invoice.ID, work)
if err == nil && !acquired {
	say("someone is charging it already")
}
let invoices = store.lock("invoices").open()?;

invoices.hold(&invoice.id, |lease| charge(&invoice, lease.fence()))?;
invoices.key(&invoice.id).wait(Duration::from_secs(30)).hold(|lease| work(lease))?;
let tried: Option<Receipt> = invoices.try_hold(&invoice.id, |lease| work(lease))?; // None when held
CallKey freeKey held
holdruns the functionwaits in line, wait at most, then in use
tryHoldruns the function{ acquired: false }, the function not run

hold answers what the function answered. wait is ten seconds unless given; a program that would wait as long as it takes says so with a longer one.

One key, one holder

A key has one holder at a time, given to waiters in the order they came (contract LOCK-01). The key is any text or number: an invoice's id, a chat's. Keys of one lock are independent: holding invoice 42 never makes invoice 43 wait.

lock invoices: key "9": held past the hold's wait: in use

Where hold may not be called

InsideWhy it is invalid
a transaction, store.tx or db.txthe transaction would hold its file's writer while the key is waited for: hold first, then begin it
a hold of the same keythe inner call would wait for the outer, which never lets go

Both fail at once, naming the lock and the key, rather than waiting until wait.

A program that holds two keys at once names them in one order: a transfer holding a then b, and another holding b then a, wait for each other until wait. Sort the keys first.

maxHold

A holder can hang: a call to a payment provider that never answers. Its lease is renewed while its process lives, so its key stays held. maxHold bounds a lease from the moment it was given, however it is renewed:

const invoices = store.lock('invoices', { ttl: '10s', maxHold: '2m' })
invoices = store.lock("invoices", ttl="10s", max_hold="2m")
invoices, err := locks.OpenLock(store, "invoices", locks.TTL(10*time.Second), locks.MaxHold(2*time.Minute))
let invoices = store.lock("invoices").max_hold(Duration::from_secs(120)).open()?;

Past it the store revokes the lease and gives the key to the next in line. The function's signal aborts, and hold fails lost once the function ends:

lock invoices: key "m": the lease was lost: it was revoked at its maxHold

maxHold is off unless given, since only the program knows how long its work may take.

Locks live in memory

A store's locks live in the memory of the process that holds it. Every holder is a thread of that process or a client of its server, so that process sees every lease and every waiter. Giving and letting go a key is a change in memory: no write, no sync. A store that opens again holds no lease.

Locks order the holders of one store; they do not coordinate two stores on two machines (NOT-03).

Errors

KindWhen
invalida bad name or key; hold inside a transaction or inside a hold of the same key
in usewait passed with the key still held
lostthe lease ran out, was revoked at maxHold, or its store closed
limitthe line of waiters for a key is full, 10,000
closedthe store closed

See also

Edit this page on GitHub Updated 19 October 2026

esc
↑↓ navigate ↵ open ⇥ filter 0 results