Skip to content

Commit 8936721

Browse files
committed
refac
1 parent d1a0fbe commit 8936721

3 files changed

Lines changed: 82 additions & 5 deletions

File tree

backend/open_webui/config.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import json
23
import logging
34
import os
@@ -35,7 +36,7 @@
3536
WEBUI_NAME,
3637
log,
3738
)
38-
from open_webui.internal.db import Base, get_db
39+
from open_webui.internal.db import Base, get_db, get_async_db
3940
from open_webui.utils.redis import get_redis_connection
4041

4142

@@ -90,6 +91,7 @@ def load_json_config():
9091

9192

9293
def save_to_db(data):
94+
"""Sync save — used ONLY at startup/import time."""
9395
with get_db() as db:
9496
existing_config = db.query(Config).first()
9597
if not existing_config:
@@ -102,12 +104,39 @@ def save_to_db(data):
102104
db.commit()
103105

104106

107+
async def async_save_to_db(data):
108+
"""Async save — used for ALL runtime config persistence."""
109+
from sqlalchemy import select
110+
111+
async with get_async_db() as db:
112+
result = await db.execute(select(Config).limit(1))
113+
existing_config = result.scalars().first()
114+
if not existing_config:
115+
new_config = Config(data=data, version=0)
116+
db.add(new_config)
117+
else:
118+
existing_config.data = data
119+
existing_config.updated_at = datetime.now()
120+
db.add(existing_config)
121+
await db.commit()
122+
123+
105124
def reset_config():
125+
"""Sync reset — used ONLY at startup."""
106126
with get_db() as db:
107127
db.query(Config).delete()
108128
db.commit()
109129

110130

131+
async def async_reset_config():
132+
"""Async reset — used at runtime."""
133+
from sqlalchemy import delete as sa_delete
134+
135+
async with get_async_db() as db:
136+
await db.execute(sa_delete(Config))
137+
await db.commit()
138+
139+
111140
# When initializing, check if config.json exists and migrate it to the database
112141
if os.path.exists(f'{DATA_DIR}/config.json'):
113142
data = load_json_config()
@@ -144,6 +173,7 @@ def get_config_value(config_path: str):
144173

145174

146175
def save_config(config):
176+
"""Sync save — used ONLY at startup/import time."""
147177
global CONFIG_DATA
148178
global PERSISTENT_CONFIG_REGISTRY
149179
try:
@@ -159,6 +189,23 @@ def save_config(config):
159189
return True
160190

161191

192+
async def async_save_config(config):
193+
"""Async save — used for ALL runtime config persistence."""
194+
global CONFIG_DATA
195+
global PERSISTENT_CONFIG_REGISTRY
196+
try:
197+
await async_save_to_db(config)
198+
CONFIG_DATA = config
199+
200+
# Trigger updates on all registered PersistentConfig entries
201+
for config_item in PERSISTENT_CONFIG_REGISTRY:
202+
config_item.update()
203+
except Exception as e:
204+
log.exception(e)
205+
return False
206+
return True
207+
208+
162209
T = TypeVar('T')
163210

164211
ENABLE_PERSISTENT_CONFIG = os.environ.get('ENABLE_PERSISTENT_CONFIG', 'True').lower() == 'true'
@@ -202,6 +249,7 @@ def update(self):
202249
log.info(f'Updated {self.env_name} to new value {self.value}')
203250

204251
def save(self):
252+
"""Sync save — used ONLY at startup/import time."""
205253
log.info(f"Saving '{self.env_name}' to the database")
206254
path_parts = self.config_path.split('.')
207255
sub_config = CONFIG_DATA
@@ -213,6 +261,19 @@ def save(self):
213261
save_to_db(CONFIG_DATA)
214262
self.config_value = self.value
215263

264+
async def async_save(self):
265+
"""Async save — used for ALL runtime config persistence."""
266+
log.info(f"Saving '{self.env_name}' to the database")
267+
path_parts = self.config_path.split('.')
268+
sub_config = CONFIG_DATA
269+
for key in path_parts[:-1]:
270+
if key not in sub_config:
271+
sub_config[key] = {}
272+
sub_config = sub_config[key]
273+
sub_config[path_parts[-1]] = self.value
274+
await async_save_to_db(CONFIG_DATA)
275+
self.config_value = self.value
276+
216277

217278
class AppConfig:
218279
_redis: Union[redis.Redis, redis.cluster.RedisCluster] = None
@@ -246,12 +307,27 @@ def __setattr__(self, key, value):
246307
self._state[key] = value
247308
else:
248309
self._state[key].value = value
249-
self._state[key].save()
310+
311+
# At runtime (inside the event loop) persist via the async engine
312+
# to avoid blocking the loop and contending with the async DB pool.
313+
# At startup/import time, fall back to sync.
314+
try:
315+
loop = asyncio.get_running_loop()
316+
loop.create_task(self._async_persist(key))
317+
except RuntimeError:
318+
self._state[key].save()
250319

251320
if self._redis and ENABLE_PERSISTENT_CONFIG:
252321
redis_key = f'{self._redis_key_prefix}:config:{key}'
253322
self._redis.set(redis_key, json.dumps(self._state[key].value))
254323

324+
async def _async_persist(self, key):
325+
"""Persist a single config key via the async engine."""
326+
try:
327+
await self._state[key].async_save()
328+
except Exception as e:
329+
log.error(f'Failed to async-persist config key {key}: {e}')
330+
255331
def __getattr__(self, key):
256332
if key not in self._state:
257333
raise AttributeError(f"Config key '{key}' not found")

backend/open_webui/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@
470470
AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH,
471471
AppConfig,
472472
reset_config,
473+
async_reset_config,
473474
)
474475
from open_webui.env import (
475476
ENABLE_CUSTOM_MODEL_FALLBACK,
@@ -625,7 +626,7 @@ async def lifespan(app: FastAPI):
625626
start_logger()
626627

627628
if RESET_CONFIG_ON_START:
628-
reset_config()
629+
await async_reset_config()
629630

630631
if LICENSE_KEY:
631632
get_license_data(app, LICENSE_KEY)

backend/open_webui/routers/configs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
1010
from open_webui.utils.auth import get_admin_user, get_verified_user
11-
from open_webui.config import get_config, save_config
11+
from open_webui.config import get_config, save_config, async_save_config
1212
from open_webui.config import BannerModel
1313

1414
from open_webui.utils.tools import (
@@ -49,7 +49,7 @@ class ImportConfigForm(BaseModel):
4949

5050
@router.post('/import', response_model=dict)
5151
async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)):
52-
save_config(form_data.config)
52+
await async_save_config(form_data.config)
5353
return get_config()
5454

5555

0 commit comments

Comments
 (0)