From 04fae8b357aed198e43455e50dd3d59d5600f6c7 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 6 Mar 2026 21:04:10 +0100 Subject: [PATCH] fix: use NullPool for SQLCipher engine to prevent segfault (#22273) The SQLCipher engine used a dummy sqlite:// URL with a creator function, which caused SQLAlchemy to auto-select SingletonThreadPool. This pool non-deterministically closes in-use connections when thread count exceeds pool_size (default 5), leading to use-after-free segfaults (exit code 139) in the native sqlcipher3 C library during multi-threaded operations like user signup. Now defaults to NullPool (each operation creates/closes its own connection) for maximum safety with the native C extension. Also respects the DATABASE_POOL_SIZE setting: if explicitly set >0, QueuePool is used with the configured pool parameters, matching the behavior of other DB paths. Fixes #22258 --- backend/open_webui/internal/db.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index 6050e37fa..afc2e7662 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -102,11 +102,30 @@ if SQLALCHEMY_DATABASE_URL.startswith("sqlite+sqlcipher://"): conn.execute(f"PRAGMA key = '{database_password}'") return conn - engine = create_engine( - "sqlite://", # Dummy URL since we're using creator - creator=create_sqlcipher_connection, - echo=False, - ) + # The dummy "sqlite://" URL would cause SQLAlchemy to auto-select + # SingletonThreadPool, which non-deterministically closes in-use + # connections when thread count exceeds pool_size, leading to segfaults + # in the native sqlcipher3 C library. Use NullPool by default for safety, + # or QueuePool if DATABASE_POOL_SIZE is explicitly configured. + if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0: + engine = create_engine( + "sqlite://", + creator=create_sqlcipher_connection, + pool_size=DATABASE_POOL_SIZE, + max_overflow=DATABASE_POOL_MAX_OVERFLOW, + pool_timeout=DATABASE_POOL_TIMEOUT, + pool_recycle=DATABASE_POOL_RECYCLE, + pool_pre_ping=True, + poolclass=QueuePool, + echo=False, + ) + else: + engine = create_engine( + "sqlite://", + creator=create_sqlcipher_connection, + poolclass=NullPool, + echo=False, + ) log.info("Connected to encrypted SQLite database using SQLCipher")