perf(images): offload validate_url() DNS resolution with asyncio.to_thread (#25825)

validate_url() calls socket.getaddrinfo() for SSRF protection, which
blocks the event loop for 100-700ms per DNS lookup. This affects:

- Image generation (get_image_data) — every external image URL
- Image editing (load_url_image) — every external image URL
- OAuth profile pictures (_process_picture_url) — every login
- Webhook delivery (post_webhook) — every notification
- Image base64 conversion (get_image_base64_from_url) — chat images

Wrap all 5 async call sites in asyncio.to_thread() so DNS resolution
runs in the thread pool. The event loop remains free to serve other
requests during the lookup.

Benchmark (3 domains, 3 trials averaged):
- BEFORE: max jitter 479ms, 1 blocked ping per trial
- AFTER:  max jitter 1ms, 0 blocked pings (324x improvement)

Co-authored-by: Tim Baek <tim@openwebui.com>
This commit is contained in:
G30
2026-06-29 02:31:24 -05:00
committed by GitHub
co-authored by Tim Baek
parent d4d9786434
commit 6ea591491e
4 changed files with 10 additions and 8 deletions
+5 -4
View File
@@ -1,3 +1,4 @@
import asyncio
import json
import logging
@@ -31,10 +32,10 @@ async def post_webhook(
) -> bool:
try:
log.debug(f'post_webhook: {url}, {message}, {event_data}')
# Block private-IP / loopback / cloud-metadata targets for every
# webhook source, including admin-managed event webhooks and
# user-configured notification URLs.
validate_url(url)
# Block private-IP / loopback / cloud-metadata targets — the URL is
# caller-controlled (user notification settings under
# ENABLE_USER_WEBHOOKS, automation notification triggers).
await asyncio.to_thread(validate_url, url)
payload = {}
# Slack and Google Chat Webhooks