Concurrency Tests¶
The tests cover file locking, thread safety, sequential access, the multi-process limitation, and a mixed OLTP-style workload.
Key Insight¶
- ❌ Multiple processes cannot open the same database (the file lock prevents it)
- ✅ Multiple threads can share one
Database(thread-safe within a process) - ✅ For multi-process access, one process runs a server and the others use HTTP
Test Cases¶
1) file lock mechanism¶
Opens a database and asserts database.lck exists while it is open. After close() it asserts the file is gone (a clean close deletes it) and opens the same path from a second Python process, asserting that succeeds, so the lock was released and not merely the handle closed.
2) thread safety¶
Creates 20 Person records and runs 4 threads that query disjoint id ranges (0-4, 5-9, 10-14, 15-19) through one shared Database at once, with bound parameters. Asserts each thread got exactly its own five ids.
3) sequential access¶
Create and insert, close, reopen and query, close, reopen and insert again. Asserts one message after the first reopen and both messages, by text, after the second.
4) concurrent access limitation¶
Holds the database open and tries to open it from a second Python process (its own JVM). Asserts the child is refused with the engine's reason, "is locked by another process". The parent closes its handle even if the check fails.
5) OLTP mixed workload (multi-thread)¶
Seeds 1,000 accounts, then 4 threads run 400 operations each, 90% point reads and 10% balance updates, retrying an update on ConcurrentModificationException. Prints throughput and latency. Asserts every operation completed and that the balances sum to the seed plus exactly the deltas that committed, so no committed update was lost.
What the lock looks like from a second process¶
This is what test_concurrent_access_limitation does:
import subprocess
import sys
import arcadedb_embedded as arcadedb
db = arcadedb.create_database("./mydb") # this process now holds the lock
child = """
import arcadedb_embedded as arcadedb
try:
arcadedb.open_database("./mydb").close()
print("OPENED")
except Exception as exc:
print("LOCKED:", exc)
"""
out = subprocess.run([sys.executable, "-c", child], capture_output=True, text=True)
print(out.stdout) # LOCKED: ... is locked by another process (path=...)
db.close()
The child sees an ArcadeDBError whose message ends with the engine's root cause, (caused by com.arcadedb.utility.LockException: Database 'mydb' is locked by another process (path=...)).
Threads: share one instance, retry conflicting writes¶
Share a single Database across threads rather than opening one per thread. Reads need no coordination. Two threads that update the same record can conflict: the losing commit raises ArcadeDBError with ConcurrentModificationException in the message, and the usual answer is to retry the transaction, as test_oltp_mixed_workload_threads does:
import time
from arcadedb_embedded.exceptions import ArcadeDBError
def add_to_balance(db, account_id, delta, retries=12):
for attempt in range(retries):
try:
with db.transaction():
db.command(
"sql",
"UPDATE Account SET balance = balance + ? WHERE account_id = ?",
delta,
account_id,
)
return
except ArcadeDBError as exc:
if "ConcurrentModificationException" not in str(exc):
raise
time.sleep(0.005 * (attempt + 1))
raise RuntimeError("update kept conflicting")
Summary Table¶
| Scenario | Supported? | Notes |
|---|---|---|
| Multiple threads, same process | ✅ Yes | Share one database instance; retry conflicting writes |
| Sequential: open → close → reopen | ✅ Yes | Close to release the lock |
| Multiple processes, embedded mode | ❌ No | The file lock refuses the second process |
Multiple processes via bundled create_server() |
✅ Yes | The owner keeps embedded access; others use HTTP |
| Multiple processes via standalone ArcadeDB server | ✅ Yes | All processes use its HTTP API |
Running These Tests¶
uv run python -m pytest bindings/python/tests/test_concurrency.py -v
# One test, with its printed output
uv run python -m pytest bindings/python/tests/test_concurrency.py::test_thread_safety -v -s
Troubleshooting¶
"is locked by another process"¶
Another process has the database open. Close it there, or have that process run a server and reach the database over HTTP.
The lock is an operating-system lock on database.lck held by the process, so it goes away when the process exits, including after a crash. A clean close also deletes the file. A database.lck left on disk therefore means the last process did not close cleanly: it blocks nothing, and the next open sees it and replays the write-ahead log. Do not delete it by hand, because that skips the recovery.
Related Documentation¶
- Access Methods - Embedded vs standalone server
- Database API - Database class reference