Skip to content

Distributed deployment: mcp/server.py get_engine() deadlocks daemon with sqlite3.OperationalError #59

Description

@unfall103-debug

Problem Description
In a distributed/containerized deployment where a master daemon (unified_daemon.py) handles background tasks 24/7 while remote or separate client worker processes invoke MCP tools (e.g., list_recent), the client process frequently crashes with:

sqlite3.OperationalError: database is locked

This occurs because every cold MCP sub‑process aggressively re‑initializes the database, colliding with the write‑locks already held by the master daemon.

Root Cause Analysis
The flaw is inside superlocalmemory/mcp/server.py in the get_engine() singleton factory:

python
def get_engine():
global _engine
if _engine is not None:
return _engine
with _engine_lock:
if _engine is not None:
return _engine
...
config = SLMConfig.load()
new_engine = MemoryEngine(config, capabilities=Capabilities.LIGHT)
new_engine.initialize() # <--- ARCHITECTURAL BUG
_engine = new_engine
return _engine
_engine_lock is an in‑process lock – it cannot coordinate between the persistent daemon and separate MCP worker processes.

Every cold worker sees _engine as None and unconditionally calls new_engine.initialize(), which executes heavy DDL (create_all_tables) on an already‑locked SQLite database.

This directly collides with the daemon’s write operations, causing immediate deadlocks.

Error Traceback
text
2026-06-21 04:07:52,075 Processing request of type CallToolRequest
2026-06-21 04:07:57,083 list_recent failed
Traceback (most recent call last):
File "…/mcp/tools_core.py", line 337, in list_recent
engine = get_engine()
File "…/mcp/server.py", line 64, in get_engine
new_engine.initialize()
File "…/core/engine.py", line 125, in initialize
self._init_db_layer()
File "…/core/engine.py", line 150, in _init_db_layer
self._db.initialize(schema)
File "…/storage/database.py", line 123, in initialize
schema_module.create_all_tables(conn)
File "…/storage/schema.py", line 713, in create_all_tables
conn.executescript(ddl)
sqlite3.OperationalError: database is locked
Proposed Fix
Intercept the lock contention gracefully, skip redundant DDL, and allow the worker to reuse the existing database connection:

python
import sqlite3
import logging

logger = logging.getLogger("superlocalmemory")

def get_engine():
global _engine
if _engine is not None:
return _engine
with _engine_lock:
if _engine is not None:
return _engine

new_engine = MemoryEngine(config, capabilities=Capabilities.LIGHT)

    # --- SAFE DISTRIBUTED PATCH ---
    try:
        new_engine.initialize()
    except sqlite3.OperationalError as e:
        if "locked" in str(e).lower():
            logger.warning(
                "Database locked by master daemon; "
                "skipping redundant schema init and reusing existing connection."
            )
            pass
        else:
            raise
    # --------------------------------

    _engine = new_engine
return _engine

Environment
SLM Version: superlocalmemory 3.6.16 (verified via slm -v)

Python: 3.13+

Host: Linux SuperLocalMemory 7.0.2-2-pve, Proxmox VE 9.2.3

Topology: Multi‑LXC distributed cluster

CT 111 (192.168.50.140) – llama.cpp

CT 111 (192.168.50.143) – MCP Hub proxy

CT 106 (192.168.50.142) – OpenClaw frontend

CT 113 (192.168.50.144) – SuperLocalMemory server (24/7 daemon)

Database: SQLite, ~41 MB, under sustained concurrent read/write load

Additional Context
This issue is distinct from the database is locked errors that occur under heavy background task concurrency (which I’ve reported separately). Here, the crash is architectural – the get_engine() design assumes a single‑process environment and cannot safely coexist with a persistent daemon in a distributed deployment.

Thank you for your work on SLM – it has become a core part of my daily workflow. I’m happy to provide additional logs or testing feedback if needed.

Best regards,
sinking

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions