Lock Service

Introduction

This page explains the design of lock service and types of locks that are possible with mechanism to lock a resource and unlock it with different possibilities.

Background

When a user want to lock a resource if we take an example of a content creator editing a content which is available for other creators to collaborate there is a possibility of content being updated by other party without knowing creator of content who is editing at the same time in this scenario we need to lock or protect  the content  being modified by other creator and as sunbird which is possible if the resource locked and it will be available to other creators when the current user who locked that content  which similar to the operation system threads problem where multiple threads want to access same resource which handled using mutex (mutual exclusion) we need to have the lock service as generalized solution for other use cases so that it can be used across the sunbird services to protect a resource whenever there is possibility of accessing multiple services or users.


Problem Statement 

Locking and unlocking of resource have multiple use cases which lock service should support

  • Locking a resource by a user or system or service
  • Unlock a resource by same user or system or service who acquired the lock
  • Force unlock a resource by admin or other system or service,  in case of race condition where the user / system is unable to unlock the resource
  • Auto unlock / Time based unlock 
  • Request for lock by user or system  when  a lock is acquired by another system or user


Solution 1 - storing lock in cassandra and unlocking with db operations and using TTL for auto unlock 


In this solution  we will be storing lock and it's info in cassandra table which will have the interface methods or api's to acquire the lock and release the lock 

and the below is the table structure for it


Lock Table

Column NameData TypeDescription
resourceIdtextResource id which is locked it is primary key
resourceTypetextType of the resource eg: content,org, user
resourceInfotextResource meta data 
creatorInfotextAdditional information of the user / system who acquired the lock
createdBytextUser or service id who acquired the lock
createdOntimestampTime when the lock is acquired
deviceIdtextThe device Id of the user
expiresAttimestampTime at which the lock will expires


here each row will have the TTL (Time To Leave) which will be configuration (By default 30 min) when a user gets the lock he will the time when the lock will be expires and he can refresh the lock before it expires the lock will provide another api to release it.

Below are the api Request and Response for lock service

API

Create

 POST - v1/lock/create

Headers:

Authorization :
"" //Bearer <token> 

x-authenticated-user-token: ""  // user token
Content-Type: "application/json" 
X-device-Id: "" // device Id generated using fingerprint2 js lib from client


REQUEST BODY :
 

{
"id": "api.lock.create",
"ver": "1.0",
"ts": "YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "did"}

,
"request":

Unknown macro: { "resourceId"}

}


 RESPONSE :

{
"id":"api.lock.create",
"ver":"1.0",
"ts":"YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "resmsgid"}

,
"responseCode": "OK",
"result":

Unknown macro: { "expiresIn"}

}



Refresh Lock

PATCH - v1/lock/refresh

Headers:

Authorization :
"" //Bearer <token> 

x-authenticated-user-token: ""  // user token 
Content-Type: "application/json"
X-device-Id: "" // device Id generated using fingerprint2 js lib from client


{
"id": "api.lock.refresh",
"ver": "1.0",
"ts": "YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "did"}

,
"request":

Unknown macro: { "resourceId"}

}

RESPONSE:

{
"id":"api.lock.refresh",
"ver":"1.0",
"ts":"YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "resmsgid"}

,
"responseCode": "OK",
"result":

Unknown macro: { "expiresIn"}

}

UnLock

DELETE - v1/lock/retire

Headers:

Authorization :
"" //Bearer <token> 

x-authenticated-user-token: ""  // user token 
Content-Type: "application/json"
X-device-Id: "" // device Id generated using fingerprint2 js lib from client



{
"id": "api.lock.retire",
"ver": "1.0",
"ts": "YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "did"}

,
"request":

Unknown macro: { "resourceId"}

}

RESPONSE:

{
"id":"api.lock.retire",
"ver":"1.0",
"ts":"YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "resmsgid"}

,
"responseCode": "OK"
}

List

POST - v1/lock/list

Headers:

Authorization :
"" //Bearer <token> 
Content-Type: "application/json"
X-device-Id: "" // device Id generated using fingerprint2 js lib from client


Request body is optional. 

{
"id": "api.lock.list",
"ver": "1.0",
"ts": "YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "did"}

,
"request": {
"filters" :

Unknown macro: { "resourceId" }

}
}


RESPONSE:

{
"id": "api.lock.list",
"ver": "1.0",
"ts":"YYYY-MM-DDThh:mm:ssZ+/-nn.nn",
"params":

Unknown macro: { "resmsgid"}

,
"responseCode": "OK",
"result": {
"count": 1,
"data": [

Unknown macro: { "resourceId"}

]
}
}



Scenarios:

Create Lock:

ScenariosStatus CodeMessage

If X-Authenticated-User-Token is not passed in header

401Required field token is missing
X-device-Id is not passed in header400X-device-Id is missing in headers
Request body is not valid. Eg - resourceId is not passed400"resourceId" is required
Trying to create lock by the user who has already locked a same resource400The resource is already locked by you in a different window/device
Trying to create lock to a resource which is already locked423
The resource is already locked by {{Lock creator}}
If resource id is not valid412Content not found with id: {{resourceId}}
If content is not in draft state412
The operation cannot be completed as content is not in draft state
If user is not a creator or collaborator of the requested resourceId412
You are not authorized
If fetching content details failed while locking a content412
Unable to fetch content details
If lock creation API fails500
Creating lock failed
If lock creation is successful200No message →  Gets expiry time in miliseconds

Refresh Lock:

ScenariosStatus CodeMessage

If X-Authenticated-User-Token is not passed in header

401Required field token is missing
X-device-Id is not passed in header400X-device-Id is missing in headers
Request body is not valid. Eg - resourceId is not passed400"resourceId" is required
Trying to refresh lock which is not locked400Resource is not locked
Trying to refresh a content which is locked by different user400
You are not authorized to refresh this resource
If resource id is not valid412Content not found with id: {{resourceId}}
If content is not in draft state412The operation cannot be completed as content is not in draft state
If user is not a creator or collaborator of the requested resourceId412You are not authorized
If fetching content details failed while refresing lock412
Unable to fetch content details
If refresh lock API fails500
Refreshing lock failed
If refreshing lock is successful200No message →  Gets expiry time in miliseconds

Retire Lock:

ScenariosStatus CodeMessage

If X-Authenticated-User-Token is not passed in header

401Required field token is missing
X-device-Id is not passed in header400X-device-Id is missing in headers
Request body is not valid. Eg - resourceId is not passed400"resourceId" is required
Trying to retire lock which is not locked400Resource is not locked
Trying to retire a content which is locked by different user400
You are not authorized to retire this resource
If resource id is not valid412Content not found with id: {{resourceId}}
If content is not in draft state412The operation cannot be completed as content is not in draft state
If user is not a creator or collaborator of the requested resourceId412You are not authorized
If fetching content details failed while retiring lock412
Unable to fetch content details
If refresh lock API fails500
Retiring lock failed
If retiring lock is successful200No message

List Lock:

ScenariosStatus CodeMessage
X-device-Id is not passed in header400X-device-Id is missing in headers
Request body is not valid. Eg - resourceId is not passed400"resourceId" is required
If list lock API fails500
Listing lock failed
If listing lock is successful with valid resourceId in param200No message →
  1. Inside result there will be count which will be 1
  2. data array with all details
If listing lock is successful without any valid resourceId in param200No message →
  1. Inside result there will be count which will be total number of locked contents
  2. data array with all details