Skip to content

Implement Locking (resource locking for concurrent access) endpoints #205

@bburda

Description

@bburda

Summary

Locks prevent concurrent modification of entity resources by multiple clients. Before a client can modify configurations, execute operations, or change modes on a locked entity, it must acquire a lock. This is important when multiple diagnostic tools or dashboards are connected simultaneously.

Locks have an expiration timer and support scoping (lock only specific resource collections).


Proposed solution

1. POST /api/v1/{entity-path}/locks

Acquire a lock on an entity (or on specific scopes within it).

Applies to entity types: Components, Apps

Path parameters:

Parameter Type Required Description
{entity-path} URL segment Yes e.g., components/engine_ecu

Request body:

{
  "lock_expiration": 600,
  "break_lock": false,
  "scopes": ["configurations", "operations"]
}
Field Type Required Description
lock_expiration integer Yes Seconds until auto-release. Must be > 0.
break_lock boolean No If true and entity is already locked by another client, break the existing lock (requires sufficient privileges). Default: false.
scopes string[] No Specific resource collections to lock (e.g., ["configurations"]). If empty/omitted, locks all resource collections on the entity.

Valid scope values: data, operations, configurations, faults, modes, scripts, bulk-data

Response 201 Created:

{
  "id": "lock_001",
  "owned": true,
  "scopes": ["configurations", "operations"],
  "lock_expiration": "2026-02-14T11:00:00Z"
}
Field Type Required Description
id string Yes Lock identifier
owned boolean Yes true if the requesting client owns this lock
scopes string[] Conditional Locked scopes (present when scoped lock)
lock_expiration string (ISO 8601) Yes When the lock expires

Error responses:

Status Error Code When
400 invalid-parameter Unknown scope, invalid expiration (≤ 0)
404 entity-not-found Entity doesn't exist
409 invalid-request Entity already locked by another client and break_lock is false or lock is not breakable

409 response when already locked:

{
  "error_code": "invalid-request",
  "message": "Entity is already locked by another client",
  "parameters": {
    "existing_lock_id": "lock_999",
    "owned": "false"
  }
}

2. GET /api/v1/{entity-path}/locks

List all active locks on an entity.

Response 200 OK:

{
  "items": [
    {
      "id": "lock_001",
      "owned": true,
      "scopes": ["configurations", "operations"],
      "lock_expiration": "2026-02-14T11:00:00Z"
    }
  ]
}

Typically there's 0 or 1 active lock. The owned field is relative to the requesting client.


3. GET /api/v1/{entity-path}/locks/{lock-id}

Read details of a specific lock.

Path parameters:

Parameter Type Required Description
{lock-id} string Yes Lock identifier

Response 200 OK: Returns a single LockInfo object.

Error: 404 if lock doesn't exist (expired or never created).


4. PUT /api/v1/{entity-path}/locks/{lock-id}

Extend (or modify) the lock's expiration. Only the lock owner can extend it.

Request body:

{
  "lock_expiration": 1200
}
Field Type Required Description
lock_expiration integer Yes New duration in seconds from now

Response 204 No Content

Error responses:

Status Error Code When
403 forbidden Client does not own this lock
404 resource-not-found Lock doesn't exist

5. DELETE /api/v1/{entity-path}/locks/{lock-id}

Release a lock. Only the lock owner (or an admin with break_lock privileges) can release it.

Response 204 No Content

Error responses:

Status Error Code When
403 forbidden Client does not own this lock and doesn't have admin privileges
404 resource-not-found Lock doesn't exist

Business Rules

Locking behavior

  1. Parent lock propagates to children: Locking a Component also locks its hosted Apps with the same scope. Locking an Area locks contained Components.
  2. Expired lock cleanup: When a lock expires, the server:
    • Terminates all in-scope temporary resources (subscriptions, triggers)
    • Removes the lock
    • Frees the entity for other clients
  3. Unlocked resources still accessible: Resources that don't have a lock requirement (per capability description) remain accessible even when the entity is locked.
  4. Lock breaking: If break_lock: true is sent and the client has sufficient privileges:
    • The existing lock is terminated
    • The original lock holder receives 409 with error code lock-broken on their next locked request
    • A new lock is created for the requesting client

Client identification

Lock ownership requires client identification. Options:

  • JWT token claim - if auth is enabled, the client is identified by their JWT token
  • Client session ID - a session cookie or custom header
  • For initial implementation: use a server-generated client ID returned at lock creation time, passed via custom header on subsequent requests

Lock enforcement in handlers

Any handler that modifies a locked resource collection must check:

  1. Is the entity locked?
  2. Does the lock scope include this resource collection?
  3. Does the requesting client own the lock?

If locked and client doesn't own it → return 409 with "lock-broken" or similar error.


Additional context

Architecture

  • Create a LockManager class with in-memory lock storage
  • Each lock: id, entity_id, client_id, scopes, expiration_time, created_at
  • Use a ROS 2 timer for periodic expiration checks
  • Lock enforcement: middleware-style check in HandlerContext or individual handlers

Route registration

srv->Post((api_path("/components") + R"(/([^/]+)/locks$)"), handler);
srv->Get((api_path("/components") + R"(/([^/]+)/locks$)"), handler);
srv->Get((api_path("/components") + R"(/([^/]+)/locks/([^/]+)$)"), handler);
srv->Put((api_path("/components") + R"(/([^/]+)/locks/([^/]+)$)"), handler);
srv->Delete((api_path("/components") + R"(/([^/]+)/locks/([^/]+)$)"), handler);
// Same for /apps/

Tests

  • Unit test: acquire lock → 201
  • Unit test: acquire while already locked → 409
  • Unit test: acquire with break_lock → new lock created
  • Unit test: extend lock → 204
  • Unit test: release lock → 204
  • Unit test: lock expiration auto-releases
  • Unit test: modify locked resource without owning lock → 409
  • Unit test: scoped lock only affects listed collections

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions