There was one bug that arose from the API work with the SQLite DB access. Here's the low-down of it:
● SQLite Cross-Thread Access Bug in EiffelWeb Application
The Problem
A Simple Showcase web application using EiffelWeb (WSF) and SQLite was experiencing intermittent PRECONDITION_VIOLATION errors in the analytics middleware. The middleware logged HTTP requests to a SQLite database, but the database file hadn't been written to in 2 days despite the server running.
Symptoms
1. Database file not being touched - showcase_dev.db last modified 12/5, while ssc_server.log was current (12/7)
2. Intermittent precondition failure - a_db_is_accessible: a_db.is_accessible in SQLITE_MODIFY_STATEMENT.make would pass multiple times, then suddenly fail
3. No obvious error - The failure happened silently during request handling
Investigation
Using EiffelStudio's debugger, we (Larry) caught the precondition violation and examined (both) the call stack:
thr_main → POOLED_THREAD → WGI_HTTPD_REQUEST → process_request →
SIMPLE_WEB_MIDDLEWARE.execute → SSC_ANALYTICS_MIDDLEWARE.process →
SSC_DATABASE.log_request → SIMPLE_SQL_DATABASE.execute_with_args →
SQLITE_MODIFY_STATEMENT.make ← PRECONDITION_VIOLATION here
The key insight: POOLED_THREAD in the call stack.
Root Cause
Cross-thread SQLite access violation.
1. The SSC_DATABASE object (containing SIMPLE_SQL_DATABASE) was created on the main thread during server startup
2. EiffelWeb's HTTP server uses a thread pool for request handling
3. When requests came in, they were processed on worker threads
4. SQLite connections opened on Thread A cannot be safely used from Thread B
5. The is_accessible check detected this invalid cross-thread access
The precondition passed during early startup (same thread) but failed once the thread pool started handling requests.
The Fix
Changed from shared database connection to per-request connections:
Before (broken):
class SSC_ANALYTICS_MIDDLEWARE
feature {NONE} -- Initialization
make (a_database: SSC_DATABASE)
do
database := a_database -- Shared across threads!
end
feature -- Processing
process (...)
do
-- ... capture request info ...
database.log_request (...) -- Called from worker thread!
end
After (working):
class SSC_ANALYTICS_MIDDLEWARE
feature {NONE} -- Initialization
make (a_db_path: STRING)
do
db_path := a_db_path -- Just store the path
end
feature -- Processing
process (...)
local
l_db: SIMPLE_SQL_DATABASE
do
-- ... capture request info ...
-- Fresh connection per request (on current worker thread)
create l_db.make (db_path)
if l_db.is_open then
l_db.execute_with_args ("INSERT INTO analytics ...", <<...>>)
l_db.close
end
end
Why Per-Request Connections?
We evaluated four options:
| Option | Verdict | Reason |
|--------------------------|---------|------------------------------------------------------|
| Per-request connection | ✅ Best | Simple, matches request lifecycle, SQLite opens fast |
| Thread-local connections | ❌ | Complex lifecycle, pool reuses threads |
| SQLite serialized mode | ❌ | Requires low-level wrapper changes |
| Queue to main thread | ❌ | Over-engineered for this use case |
SQLite connections are essentially file handles - opening is sub-millisecond. This is the standard pattern used by Django, Rails, and most web frameworks.
Lessons Learned
1. EiffelWeb uses thread pools - Don't assume single-threaded execution
2. SQLite is not thread-safe by default - Connections are bound to creating thread
3. Design by Contract caught it - The is_accessible precondition detected the invalid state
4. File timestamps are diagnostic gold - The unchanged .db file immediately indicated writes weren't happening
Environment
- EiffelStudio 25.02
- Windows 11
- EiffelWeb (WSF) with standalone connector
- SQLite via eiffel_sqlite wrapper
- simple_sql facade library
Honestly, I would not have caught the bug because of my own lack of knowledge. With the AI, not only did I get the bug fixed, but I learned a valuable lesson about how this system works. Noodling out bugs with AI + DBC/Eiffel is far superior to operating alone. What you don't know can and will hurt you.
Best,
Larry