A gRPC, REST and TCP server to provide FIFO named locks
1.8K
A gRPC, REST and TCP server to provide FIFO named locks
Default local ports:
Default docker exposed ports:
Proto file: here
Pull the image
docker pull paoloiommarini/lock-provider:latest
Run the container (in this example the server is reachable at localhost:5200)
docker run --name LockProvider -p 5200:5000 -p 5201:5001 -p 5202:5002 -d --restart unless-stopped paoloiommarini/lock-provider
Get the status of the server.
Request:
{}
Response:
{
"serverVersion": "1.0.0.0",
"uptime": "00:00:05.6849020",
"locks": 1,
"waitingLocks": 1,
"timeStamp": "2025-06-13T16:42:51.8657848Z"
}
Get a list of acquired locks.
The owner must match exactly.
The name is a regex to filter locks
Request:
{
"owner": "test",
"name": "*"
}
Response:
{
"owner": "test",
"name": "*",
"count": 1,
"locks": [
{
"owner": "test",
"name": "test",
"acquiredAt": "2025-06-13T16:41:46.0393747Z"
}
],
"timeStamp": "2025-06-13T16:41:42.0393747Z"
}
Try to acquire a lock.
Locks must be unique by owner and name, this means that different owners can acquire a lock with the same name
If timeToLive is greater than zero the lock will be automatically released after timeToLive seconds.
Request:
{
"owner": "test",
"name": "test",
"timeout": 5,
"timeToLive": 10
}
Response:
{
"owner": "test",
"name": "test",
"result": "True",
"timeStamp": "2025-06-13T16:47:58.6890059Z"
}
If the lock cannot be acquired the error property contains the error message
{
"owner": "test",
"name": "test",
"result": "False",
"error": "Timeout",
"timeStamp": "2025-06-13T16:56:18.3054261Z"
}
Check if a name is locked
Request:
{
"owner": "test",
"name": "test"
}
Response:
{
"owner": "test",
"name": "test",
"result": "True",
"timeStamp": "2025-06-13T16:50:00.7567875Z"
}
Release a lock
Request:
{
"owner": "test",
"name": "test"
}
Response:
{
"owner": "test",
"name": "test",
"result": "True",
"timeStamp": "2025-06-13T16:45:20.4941284Z"
}
If the lock cannot be released (i.e., the lock was not found)
{
"owner": "test",
"name": "test",
"result": "False",
"error": "NotFound",
"timeStamp": "2025-06-13T17:06:55.1440345Z"
}
Release multiple locks
The owner must match exactly.
The name is a regex to filter locks
Request:
{
"owner": "test",
"name": "*"
}
Response:
{
"owner": "test",
"name": "*",
"count": 1,
"locks": [
{
"owner": "test",
"name": "lock 1",
"acquiredAt": "2025-06-14T07:17:58.3147789Z"
}
],
"timeStamp": "2025-06-14T07:17:59.1440345Z"
}
Swagger is available at http://localhost:5001/swagger
A command is composed by the command name followed by a number of arguments in the format name=value separated by a semicolon ;
Each command must end with a newline character \n
Each command must have a Id argument. The Id is returned in the server response.
Command:
ACQUIRE;Id=123-456;Owner=lockOwner;Name=lockName;Timeout=10;TimeToLive=10;
Response:
Id=123-456;Name=lockName;Owner=lockOwner;Result=True;TimeStamp=2025-12-26T16:30:09.1702406Z;
Command:
RELEASE;Id=789-012;Owner=lockOwner;Name=lockName;
Response:
Id=789-012;Name=lockName;Owner=lockOwner;Result=True;TimeStamp=2025-12-26T16:30:46.7478962Z;
Command:
RELEASEMANY;Id=543-210;Owner=lockOwner;Name=*;
Response:
Id=543-210;Name=lockName;Owner=*;Result=True;Count=4;TimeStamp=2025-12-26T16:30:46.7478962Z;
Create a new Node project
mkdir lock-provider-quickstart
cd lock-provider-quickstart
mkdir src
pnpm init
pnpm add -D typescript @types/node
pnpm add --save @grpc/grpc-js @grpc/proto-loader @bufbuild/protobuf
pnpm install --save-dev grpc-tools grpc_tools_node_protoc_ts ts-proto
npx tsc --init
Set the file tsconfig.json with this content
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src"]
}
Edit the scripts section in the file package.json
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon --watch src --exec ts-node src/index.ts"
},
Copy the proto file in the lock-provider-quickstart directory.
Create the typed client
npx protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./src --ts_proto_opt=outputServices=grpc-js -I ./ ./lock-provider.proto
Create the file src/index.ts with this content
import { ChannelCredentials } from "@grpc/grpc-js";
import { LockAcquireRequest, LockProviderClient, LockRequest, LockResponse, LocksListResponse } from "./lock-provider";
const client = new LockProviderClient('localhost:5200', ChannelCredentials.createInsecure());
function releaseMany(request: LockRequest): Promise<LocksListResponse> {
return new Promise((resolve, reject) => {
client.releaseMany(request, (err, response) => {
if (err) {
reject(err);
} else {
if (response.result !== 'True') {
reject(response.error);
return;
}
resolve(response);
}
});
});
}
function acquireLock(request: LockAcquireRequest): Promise<LockResponse> {
return new Promise((resolve, reject) => {
client.acquire(request, (err, response) => {
if (err) {
console.error(`Lock ${request.name} acquire failed:`, err);
reject(err);
} else {
if (response.result !== 'True') {
console.error(`Lock ${request.name} acquire failed:`, response.error);
reject(response.error);
return;
}
console.error(`Lock ${request.name} acquired`);
resolve(response);
}
});
});
}
function releaseLock(request: LockRequest): Promise<LockResponse> {
return new Promise((resolve, reject) => {
client.release(request, (err, response) => {
if (err) {
console.error(`Lock ${request.name} release failed:`, err);
reject(err);
} else {
if (response.result !== 'True') {
console.error(`Lock ${request.name} release failed:`, response.error);
reject(response.error);
return;
}
console.error(`Lock ${request.name} released`);
resolve(response);
}
});
});
}
(async () => {
const owner: string = 'lock_owner';
try {
// Acquire lock_1
await acquireLock({
owner,
name: 'lock_1',
timeout: 5
});
// Release lock_1
await releaseLock({
owner,
name: 'lock_1'
})
// Acquire lock_2
await acquireLock({
owner,
name: 'lock_2',
timeout: 5
});
// Try to acquire lock_2. This will fail after 5 seconds
await acquireLock({
owner,
name: 'lock_2',
timeout: 5
});
} catch (err) {
console.error('Exception:', err);
} finally {
// Release all lock for this owner
const rsm = await releaseMany({
owner,
name: '*',
});
console.log(`Released ${rsm.count} locks`);
}
client.close();
process.exit(0);
})();
Build the project
pnpm build
Run the project
pnpm start
Expected output
Lock lock_1 acquired
Lock lock_1 released
Lock lock_2 acquired
Lock lock_2 acquire failed: Timeout
Exception: Timeout
Released 1 locks
Content type
Image
Digest
sha256:45ca6261d…
Size
95.1 MB
Last updated
9 months ago
docker pull paoloiommarini/lock-provider