This project has been archived by its maintainers, and is no longer receiving any updates.
dflockd
A lightweight distributed lock server using a simple line-based TCP protocol with FIFO ordering, automatic lease expiry, and background renewal.
Quick start
# Install
uv sync
# Run the server
uv run dflockd
The server listens on 0.0.0.0:6388 by default.
Server configuration
All tuning is via environment variables:
| Variable | Default | Description |
|---|---|---|
DFLOCKD_HOST |
0.0.0.0 |
Bind address |
DFLOCKD_PORT |
6388 |
Bind port |
DFLOCKD_DEFAULT_LEASE_TTL_S |
33 |
Default lock lease duration (seconds) |
DFLOCKD_LEASE_SWEEP_INTERVAL_S |
1 |
How often to check for expired leases |
DFLOCKD_GC_LOOP_SLEEP |
5 |
How often to prune idle lock state |
DFLOCKD_GC_MAX_UNUSED_TIME |
60 |
Seconds before idle lock state is pruned |
DFLOCKD_MAX_LOCKS |
1024 |
Maximum number of unique lock keys |
DFLOCKD_READ_TIMEOUT_S |
23 |
Client read timeout (seconds) |
DFLOCKD_AUTO_RELEASE_ON_DISCONNECT |
1 |
Release locks when a client disconnects (1, yes, true to enable) |
CLI arguments
Settings can also be passed as command-line flags. Environment variables take precedence over CLI arguments.
uv run dflockd --port 7000 --max-locks 512
| Flag | Default | Env var override |
|---|---|---|
--host |
0.0.0.0 |
DFLOCKD_HOST |
--port |
6388 |
DFLOCKD_PORT |
--default-lease-ttl |
33 |
DFLOCKD_DEFAULT_LEASE_TTL_S |
--lease-sweep-interval |
1 |
DFLOCKD_LEASE_SWEEP_INTERVAL_S |
--gc-interval |
5 |
DFLOCKD_GC_LOOP_SLEEP |
--gc-max-idle |
60 |
DFLOCKD_GC_MAX_UNUSED_TIME |
--max-locks |
1024 |
DFLOCKD_MAX_LOCKS |
--read-timeout |
23 |
DFLOCKD_READ_TIMEOUT_S |
--auto-release-on-disconnect / --no-auto-release-on-disconnect |
true |
DFLOCKD_AUTO_RELEASE_ON_DISCONNECT |
Protocol
The wire protocol is line-based UTF-8 over TCP. Each request is exactly 3 lines: command\nkey\narg\n.
Commands
Lock (acquire)
l
<key>
<timeout_s> [<lease_ttl_s>]
Response: ok <token> <lease_ttl>\n or timeout\n
Renew
n
<key>
<token> [<lease_ttl_s>]
Response: ok <seconds_remaining>\n or error\n
Enqueue (two-phase step 1)
e
<key>
[<lease_ttl_s>]
Response: acquired <token> <lease_ttl>\n or queued\n
Wait (two-phase step 2)
w
<key>
<timeout_s>
Response: ok <token> <lease_ttl>\n or timeout\n
Release
r
<key>
<token>
Response: ok\n or error\n
Behavior
- Locks are granted in strict FIFO order per key.
- Leases expire automatically if not renewed. On expiry, the lock passes to the next waiter.
- Connections that disconnect have their held locks released automatically.
Client usage
Async client
import asyncio
from dflockd.client import DistributedLock
async def main():
async with DistributedLock("my-key", acquire_timeout_s=10) as lock:
print(lock.token, lock.lease)
# critical section — lease auto-renews in background
asyncio.run(main())
Sync client
from dflockd.sync_client import DistributedLock
with DistributedLock("my-key", acquire_timeout_s=10) as lock:
print(lock.token, lock.lease)
# critical section — lease auto-renews in background thread
Manual acquire/release
Both clients support explicit acquire() / release() outside of a context manager:
from dflockd.sync_client import DistributedLock
lock = DistributedLock("my-key")
if lock.acquire():
try:
pass # critical section
finally:
lock.release()
Two-phase lock acquisition
The enqueue() / wait() methods split lock acquisition into two steps, allowing you to notify an external system after joining the queue but before blocking:
from dflockd.sync_client import DistributedLock
lock = DistributedLock("my-key")
status = lock.enqueue() # join queue, returns "acquired" or "queued"
notify_external_system() # your application logic here
if lock.wait(timeout_s=10): # block until granted (no-op if already acquired)
try:
pass # critical section
finally:
lock.release()
Async equivalent:
lock = DistributedLock("my-key")
status = await lock.enqueue()
await notify_external_system()
if await lock.wait(timeout_s=10):
try:
pass # critical section
finally:
await lock.release()
Parameters
| Parameter | Default | Description |
|---|---|---|
key |
(required) | Lock name |
acquire_timeout_s |
10 |
Seconds to wait for lock acquisition |
lease_ttl_s |
None (server default) |
Lease duration in seconds |
servers |
[("127.0.0.1", 6388)] |
List of (host, port) tuples |
sharding_strategy |
stable_hash_shard |
Callable[[str, int], int] — maps (key, num_servers) to server index |
renew_ratio |
0.5 |
Renew at lease * ratio seconds |
Multi-server sharding
When running multiple dflockd instances, the client can distribute keys across servers using consistent hashing. Each key always routes to the same server.
from dflockd.sync_client import DistributedLock
servers = [("server1", 6388), ("server2", 6388), ("server3", 6388)]
with DistributedLock("my-key", servers=servers) as lock:
print(lock.token, lock.lease)
The default strategy uses zlib.crc32 for stable, deterministic hashing. You can provide a custom strategy:
from dflockd.sync_client import DistributedLock
def my_strategy(key: str, num_servers: int) -> int:
"""Route all keys to the first server."""
return 0
with DistributedLock("my-key", servers=servers, sharding_strategy=my_strategy) as lock:
pass
Metadata
Release files for dflockd 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dflockd-0.5.0.tar.gz | 13.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dflockd-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.0 kB
Release files / dflockd-0.5.0.tar.gz
| Download URL | dflockd-0.5.0.tar.gz |
|---|---|
| Size | 13.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1d57b5dadac18e01e3cb5febd5f1fdffb63e3451a983a1d9ef5ec26a8098b80f
|
|
BLAKE2b-256 checksum How to use checksums |
5729e2fba0799dbc7977ccc05e7d19edb8768d4436db0d280b1b7bbcc695ba15
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / dflockd-0.5.0-py3-none-any.whl
| Download URL | dflockd-0.5.0-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7ab984d653855ba4680cae13fa7255c19d8d92edd4fa0f271c8da42af21444aa
|
|
BLAKE2b-256 checksum How to use checksums |
9c2477cf3010f18bb61f369440f15592082bfc68178648fe67bade1a1810184b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|