---
question: How do I take a lock only the holder can release?
kind: task
via_web: na
via_cli: na
via_python: available
via_api: na
via_config: na
ref_python: set_if_not_exists
prereq: An in-engine module that lists `KeyValueService` in its dependencies. See [Store a value](/user-guide/build/platform/store-a-value?via=python).
next:
  - /capabilities/build/platform/store-a-value
  - /capabilities/build/modules/module-jobs
---

# Compare and set

The key-value service has two atomic checks, and a lock built from them. It has no single call that replaces a value only when the old bytes match.

## Web UI

Not applicable. System › Data › Key-value writes and deletes keys unconditionally, after you type the key back. Use the Python steps on this page.

## CLI

Not applicable. There is no `abi` command for the key-value store. Use the Python steps on this page.

## Python

### Write only when the key is absent

```python
kv = self.engine.services.kv
wrote = kv.set_if_not_exists("my_module:lock:job", b"token", ttl=30)
```

It returns `True` when it wrote, `False` when the key was already there.

### Delete only when the value matches

```python
removed = kv.delete_if_value_matches("my_module:lock:job", b"token")
```

It returns `True` when the stored bytes equalled the value and the key was deleted.

### Or take a lock

`lock` is those two calls as a context manager:

```python
from naas_abi_core.services.keyvalue.KeyValuePorts import KVLockTimeoutError

try:
    with kv.lock("my_module:lock:job", ttl=60, timeout=30):
        ...
except KVLockTimeoutError:
    ...  # someone else holds it
```

The lock writes a random token with `set_if_not_exists` and releases with `delete_if_value_matches`, so only the holder releases it. The defaults are `ttl=30`, `timeout=10.0` and `retry_delay=0.05`. Keep `ttl` longer than the block: if the process dies, the key expires and frees the lock.

:::note Across processes
The lock is shared only when the store is: `redis`, or the `python` adapter on one SQLite file. The `python` adapter without `persistence_path` is in memory, so each process has its own.
:::

## REST API

Not applicable. The System routes of the Nexus API write and delete keys unconditionally; none of them compares first. Use the Python steps on this page.

## Config

Not applicable. A conditional write is a call, not a setting.
