flush returns 500 when SQLite write hits the busy timeout
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 55/100
Línea de trabajo
Start with grep -rn "OperationalError" src/everos/core/persistence/sqlite/ to find where lock timeouts escape, and read _apply_pragmas in core/persistence/sqlite/engine.py. The fix either retries database is locked with bounded backoff or maps it to a 503 with Retry-After next to the existing EXTERNAL_SERVICE_UNAVAILABLE handling. Done means a test that holds a write transaction past busy_timeout_ms on a second connection gets a retryable response instead of a 500. The design choice between retry and status mapping is not settled in the issue, so confirm it with maintainers first.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
What happened
During a 10-hour soak at sustained load, one POST /api/v2/memory/flush returned
500 INTERNAL_ERROR. The cause was SQLite lock contention:
2026-10-13T14:12:17Z [error] unhandled_exception
exception_type=OperationalError path=/api/v2/memory/flush
OperationalError: (sqlite3.OperationalError) database is locked
The write waited out PRAGMA busy_timeout (default busy_timeout_ms = 5000) and
the error propagated uncaught through the middleware stack to the client.
Why this is worth fixing
Lock contention under concurrent writers is expected and retryable. The
problem is the status it is reported as: a caller receiving 500 INTERNAL_ERROR
has no way to tell "the server is broken" from "try again in a moment", so the
only safe client behaviour is to give up or to retry blindly on a code that
normally means retrying is pointless.
The other three failures in the same run were all external-provider issues and
were surfaced correctly as 503 EXTERNAL_SERVICE_UNAVAILABLE — so the contrast
is visible within one soak: a transient, retryable condition we own is the one
reported as an internal fault.
How often
| Run | Platform | Duration | Writes | Occurrences |
|---|---|---|---|---|
| soak, real providers, 2 writers + 4 readers | macOS 15.6 (arm64) | 10.03 h | 3003 | 1 |
| soak, stub providers, high write rate | Ubuntu (x86_64) | 10 h | 14398 | 0 |
| soak, real providers, 2 writers + 4 readers | Windows 11 (x86_64) | 2.08 h | 570 | 0 |
Roughly 0.03% of writes on the one run that hit it. Rare, but it scales with
concurrency, and the hosted/multi-writer deployments are exactly where it gets
more likely.
Configuration in effect
Defaults, nothing tuned:
| Setting | Value |
|---|---|
sqlite.journal_mode |
WAL |
sqlite.busy_timeout_ms |
5000 |
sqlite.pool_size |
5 |
sqlite.max_overflow |
10 |
sqlite.pool_timeout_seconds |
30.0 |
The PRAGMAs are applied per connection in
core/persistence/sqlite/engine.py::_apply_pragmas, and they are being applied
correctly — this is not a misconfiguration, the writer genuinely waited 5s.
Suggested direction
Either would remove the misleading status; the first also removes the failure:
- Retry the write inside the persistence layer on
sqlite3.OperationalErrorwhose message isdatabase is locked, with a
short bounded backoff.grep -rn "OperationalError" src/everos/core/persistence/sqlite/
currently returns no handler, so every lock timeout becomes an unhandled
exception. - Map it to a retryable status —
503withRetry-After, alongside the
existingEXTERNAL_SERVICE_UNAVAILABLEmapping — so a caller can act on it.
Raising busy_timeout_ms alone would only move the threshold, not change what
the caller sees when it is crossed.
Reproducing
Not reproducible on demand — it took 10 hours of sustained concurrent load to
surface once. A targeted test is cheaper than a soak: hold a write transaction
open past busy_timeout_ms on a second connection and assert the response is
retryable rather than 500.
Evidence
- Server log:
unhandled_exceptionat2026-10-13T14:12:17.283268Z - Soak result:
totals.errors = 4, of which this is the only non-external one - Commit under test:
c36a986 - Note: the rendered traceback captured only the middleware frames
(core/middleware/request_id.py:51,core/middleware/profile.py:54) above
the SQLAlchemy/aiosqlite stack, so the exact repository call site is not in
the log.
- Lenguaje dominante
- Python
- Estrellas
- 13.3k
- Forks
- 926
- Merge medio
- 17 h 8 min
- PR fusionados (30 d)
- 32
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de EverMind-AI/EverOS
-
Dificultad 2/5 Medio día Aptitud para principiantes 62/100
EverMind-AI/EverOS#414 ·
Los mantenedores suelen responder en 1 día
-
[Offer] Free independent recomputation of your published memory benchmarks (assayed-tier upgrade)Abierto
Dificultad 4/5 1-2 días Aptitud para principiantes 3/100
EverMind-AI/EverOS#504 ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
EverMind-AI/EverOS#501 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 35/100
EverMind-AI/EverOS#485 ·
Los mantenedores suelen responder en 1 día
-
memory: add POST /api/v2/memory/delete so everos-mcp forget_session works on self-hosted serversAbiertoenhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 12/100
EverMind-AI/EverOS#483 ·
Los mantenedores suelen responder en 1 día
Todos los issues de EverMind-AI/EverOS
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
ok-oldking/ok-wuthering-waves#1785 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
healthchecks/healthchecks#1349 ·
-
deployment release-lag
Dificultad 1/5 Menos de una hora Aptitud para principiantes 68/100
nolte/kamerplanter#2374 ·
Los mantenedores suelen responder en 1 día
-
bug v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/python-sdk#3725 ·
Los mantenedores suelen responder en 1 día
-
batchterms() drops queries without a similar() clause, misaligning results with inputsPosiblemente ocupada @MohammadHijjawi97 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Los mantenedores suelen responder en 1 día