[ВОЛК] увеличение таймаутов на запись и в gunicorn
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -6,7 +6,7 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/opt/testbot-git
|
WorkingDirectory=/opt/testbot-git
|
||||||
ExecStart=/opt/testbot-git/venv/bin/gunicorn --bind 192.168.0.109:5006 --workers 4 --timeout=120 wsgi_edit:app
|
ExecStart=/opt/testbot-git/venv/bin/gunicorn --bind 192.168.0.109:5006 --workers 4 --timeout=240 wsgi_edit:app
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
+73
-45
@@ -155,10 +155,11 @@ async def publish_to_tg(vk_post_id):
|
|||||||
|
|
||||||
# Создаем httpx клиент с отключенным HTTP/2
|
# Создаем httpx клиент с отключенным HTTP/2
|
||||||
# Используем более детальные таймауты: connect, read, write, pool
|
# Используем более детальные таймауты: connect, read, write, pool
|
||||||
|
# Таймаут на запись увеличен для больших файлов (до 10 МБ)
|
||||||
timeout_config = httpx.Timeout(
|
timeout_config = httpx.Timeout(
|
||||||
connect=10.0, # Таймаут на подключение
|
connect=10.0, # Таймаут на подключение
|
||||||
read=60.0, # Таймаут на чтение (увеличен для больших изображений)
|
read=120.0, # Таймаут на чтение (увеличен для больших изображений)
|
||||||
write=30.0, # Таймаут на запись
|
write=120.0, # Таймаут на запись (увеличен для больших файлов)
|
||||||
pool=10.0 # Таймаут на получение соединения из пула
|
pool=10.0 # Таймаут на получение соединения из пула
|
||||||
)
|
)
|
||||||
httpx_client = httpx.AsyncClient(
|
httpx_client = httpx.AsyncClient(
|
||||||
@@ -234,67 +235,94 @@ async def publish_to_tg(vk_post_id):
|
|||||||
logger.info(f"Длина подписи: {len(text)} символов")
|
logger.info(f"Длина подписи: {len(text)} символов")
|
||||||
|
|
||||||
# Скачиваем изображение
|
# Скачиваем изображение
|
||||||
|
image_data = None
|
||||||
try:
|
try:
|
||||||
image_data = await download_image(image_url, httpx_client)
|
image_data = await download_image(image_url, httpx_client)
|
||||||
except Exception as download_error:
|
except Exception as download_error:
|
||||||
logger.error(f"Ошибка при скачивании изображения для записи VK ID {vk_post_id}: {download_error}")
|
logger.error(f"Ошибка при скачивании изображения для записи VK ID {vk_post_id}: {download_error}")
|
||||||
raise # Пробрасываем ошибку, чтобы перейти к fallback
|
raise # Пробрасываем ошибку, чтобы перейти к fallback
|
||||||
|
|
||||||
# Логируем время начала запроса
|
# Вычисляем динамический таймаут на запись в зависимости от размера файла
|
||||||
request_start_time = time.time()
|
# Базовый таймаут 60 секунд + 10 секунд на каждый МБ (минимум 120 секунд)
|
||||||
logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}")
|
file_size_mb = len(image_data) / (1024 * 1024)
|
||||||
|
write_timeout = max(120.0, 60.0 + (file_size_mb * 10.0))
|
||||||
|
logger.info(f"Размер файла: {file_size_mb:.2f} МБ, таймаут на запись: {write_timeout:.1f} секунд")
|
||||||
|
|
||||||
# Отправляем изображение как файл через multipart/form-data
|
# Создаем клиент с увеличенным таймаутом на запись для больших файлов
|
||||||
# Создаем BytesIO объект для файла
|
upload_timeout_config = httpx.Timeout(
|
||||||
image_file = io.BytesIO(image_data)
|
connect=10.0,
|
||||||
files = {
|
read=120.0,
|
||||||
'photo': ('image.jpg', image_file, 'image/jpeg')
|
write=write_timeout, # Динамический таймаут на запись
|
||||||
}
|
pool=10.0
|
||||||
data = {
|
)
|
||||||
'chat_id': CHANNEL_ID,
|
upload_client = httpx.AsyncClient(
|
||||||
'caption': text,
|
http2=False,
|
||||||
'parse_mode': 'HTML',
|
timeout=upload_timeout_config,
|
||||||
'disable_notification': str(PUBLISH_SILENTLY).lower()
|
follow_redirects=True
|
||||||
}
|
)
|
||||||
|
|
||||||
photo_response = await httpx_client.post(send_photo_url, files=files, data=data)
|
try:
|
||||||
request_duration = time.time() - request_start_time
|
# Логируем время начала запроса
|
||||||
logger.info(f"Запрос отправки фото завершен за {request_duration:.2f} секунд")
|
request_start_time = time.time()
|
||||||
|
logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}")
|
||||||
|
|
||||||
if photo_response.status_code == 200:
|
# Отправляем изображение как файл через multipart/form-data
|
||||||
photo_result = photo_response.json()
|
# Создаем BytesIO объект для файла
|
||||||
if photo_result.get('ok'):
|
image_file = io.BytesIO(image_data)
|
||||||
message_data = photo_result.get('result', {})
|
files = {
|
||||||
message_id = message_data.get('message_id')
|
'photo': ('image.jpg', image_file, 'image/jpeg')
|
||||||
message_has_image = True # Успешно отправили изображение
|
}
|
||||||
|
data = {
|
||||||
|
'chat_id': CHANNEL_ID,
|
||||||
|
'caption': text,
|
||||||
|
'parse_mode': 'HTML',
|
||||||
|
'disable_notification': str(PUBLISH_SILENTLY).lower()
|
||||||
|
}
|
||||||
|
|
||||||
|
photo_response = await upload_client.post(send_photo_url, files=files, data=data)
|
||||||
|
request_duration = time.time() - request_start_time
|
||||||
|
logger.info(f"Запрос отправки фото завершен за {request_duration:.2f} секунд")
|
||||||
|
|
||||||
|
if photo_response.status_code == 200:
|
||||||
|
photo_result = photo_response.json()
|
||||||
|
if photo_result.get('ok'):
|
||||||
|
message_data = photo_result.get('result', {})
|
||||||
|
message_id = message_data.get('message_id')
|
||||||
|
message_has_image = True # Успешно отправили изображение
|
||||||
|
else:
|
||||||
|
# Ошибка в ответе API - логируем полную информацию
|
||||||
|
log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]")
|
||||||
|
error_code = photo_result.get('error_code')
|
||||||
|
if error_code == 429:
|
||||||
|
retry_after = photo_result.get('parameters', {}).get('retry_after', 60)
|
||||||
|
raise RetryAfterException(retry_after)
|
||||||
|
raise Exception(f"API error: {photo_result.get('description', 'Unknown error')}")
|
||||||
else:
|
else:
|
||||||
# Ошибка в ответе API - логируем полную информацию
|
|
||||||
log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]")
|
log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]")
|
||||||
error_code = photo_result.get('error_code')
|
if photo_response.status_code == 429:
|
||||||
if error_code == 429:
|
retry_after = 60
|
||||||
retry_after = photo_result.get('parameters', {}).get('retry_after', 60)
|
try:
|
||||||
|
error_data = photo_response.json()
|
||||||
|
retry_after = error_data.get('parameters', {}).get('retry_after', 60)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
raise RetryAfterException(retry_after)
|
raise RetryAfterException(retry_after)
|
||||||
raise Exception(f"API error: {photo_result.get('description', 'Unknown error')}")
|
raise Exception(f"HTTP {photo_response.status_code}: {photo_response.text}")
|
||||||
else:
|
finally:
|
||||||
log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]")
|
await upload_client.aclose()
|
||||||
if photo_response.status_code == 429:
|
|
||||||
retry_after = 60
|
|
||||||
try:
|
|
||||||
error_data = photo_response.json()
|
|
||||||
retry_after = error_data.get('parameters', {}).get('retry_after', 60)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
raise RetryAfterException(retry_after)
|
|
||||||
raise Exception(f"HTTP {photo_response.status_code}: {photo_response.text}")
|
|
||||||
except RetryAfterException:
|
except RetryAfterException:
|
||||||
raise
|
raise
|
||||||
except httpx.TimeoutException as e:
|
except (httpx.TimeoutException, httpx.WriteTimeout, httpx.ReadTimeout) as e:
|
||||||
logger.error(f"ТАЙМАУТ при отправке изображения для записи VK ID {vk_post_id}")
|
timeout_type = type(e).__name__
|
||||||
logger.error(f"Тип исключения: {type(e).__name__}")
|
logger.error(f"ТАЙМАУТ ({timeout_type}) при отправке изображения для записи VK ID {vk_post_id}")
|
||||||
|
logger.error(f"Тип исключения: {timeout_type}")
|
||||||
logger.error(f"Сообщение об ошибке: {str(e)}")
|
logger.error(f"Сообщение об ошибке: {str(e)}")
|
||||||
logger.error(f"Полная информация об исключении: {repr(e)}")
|
logger.error(f"Полная информация об исключении: {repr(e)}")
|
||||||
if hasattr(e, 'request'):
|
if hasattr(e, 'request'):
|
||||||
logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}")
|
logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}")
|
||||||
|
# Для WriteTimeout логируем размер файла
|
||||||
|
if isinstance(e, httpx.WriteTimeout) and image_data is not None:
|
||||||
|
logger.error(f"Размер файла, вызвавшего таймаут записи: {len(image_data) / (1024 * 1024):.2f} МБ")
|
||||||
logger.error(f"Трассировка стека:\n{traceback.format_exc()}")
|
logger.error(f"Трассировка стека:\n{traceback.format_exc()}")
|
||||||
raise # Пробрасываем таймаут наверх
|
raise # Пробрасываем таймаут наверх
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user