diff --git a/services/testedit-git.service b/services/testedit-git.service index 699f027..e07e0b1 100644 --- a/services/testedit-git.service +++ b/services/testedit-git.service @@ -6,7 +6,7 @@ After=network.target [Service] User=root 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 [Install] diff --git a/tg_publish.py b/tg_publish.py index 465919f..531328a 100644 --- a/tg_publish.py +++ b/tg_publish.py @@ -155,10 +155,11 @@ async def publish_to_tg(vk_post_id): # Создаем httpx клиент с отключенным HTTP/2 # Используем более детальные таймауты: connect, read, write, pool + # Таймаут на запись увеличен для больших файлов (до 10 МБ) timeout_config = httpx.Timeout( connect=10.0, # Таймаут на подключение - read=60.0, # Таймаут на чтение (увеличен для больших изображений) - write=30.0, # Таймаут на запись + read=120.0, # Таймаут на чтение (увеличен для больших изображений) + write=120.0, # Таймаут на запись (увеличен для больших файлов) pool=10.0 # Таймаут на получение соединения из пула ) httpx_client = httpx.AsyncClient( @@ -234,67 +235,94 @@ async def publish_to_tg(vk_post_id): logger.info(f"Длина подписи: {len(text)} символов") # Скачиваем изображение + image_data = None try: image_data = await download_image(image_url, httpx_client) except Exception as download_error: logger.error(f"Ошибка при скачивании изображения для записи VK ID {vk_post_id}: {download_error}") raise # Пробрасываем ошибку, чтобы перейти к fallback - # Логируем время начала запроса - request_start_time = time.time() - logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}") + # Вычисляем динамический таймаут на запись в зависимости от размера файла + # Базовый таймаут 60 секунд + 10 секунд на каждый МБ (минимум 120 секунд) + 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 объект для файла - image_file = io.BytesIO(image_data) - files = { - 'photo': ('image.jpg', image_file, 'image/jpeg') - } - data = { - 'chat_id': CHANNEL_ID, - 'caption': text, - 'parse_mode': 'HTML', - 'disable_notification': str(PUBLISH_SILENTLY).lower() - } + # Создаем клиент с увеличенным таймаутом на запись для больших файлов + upload_timeout_config = httpx.Timeout( + connect=10.0, + read=120.0, + write=write_timeout, # Динамический таймаут на запись + pool=10.0 + ) + upload_client = httpx.AsyncClient( + http2=False, + timeout=upload_timeout_config, + follow_redirects=True + ) - photo_response = await httpx_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 # Успешно отправили изображение + try: + # Логируем время начала запроса + request_start_time = time.time() + logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}") + + # Отправляем изображение как файл через multipart/form-data + # Создаем BytesIO объект для файла + image_file = io.BytesIO(image_data) + files = { + 'photo': ('image.jpg', image_file, 'image/jpeg') + } + 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: - # Ошибка в ответе 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) + 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"API error: {photo_result.get('description', 'Unknown error')}") - else: - log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]") - 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}") + raise Exception(f"HTTP {photo_response.status_code}: {photo_response.text}") + finally: + await upload_client.aclose() except RetryAfterException: raise - except httpx.TimeoutException as e: - logger.error(f"ТАЙМАУТ при отправке изображения для записи VK ID {vk_post_id}") - logger.error(f"Тип исключения: {type(e).__name__}") + except (httpx.TimeoutException, httpx.WriteTimeout, httpx.ReadTimeout) as e: + timeout_type = 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"Полная информация об исключении: {repr(e)}") if hasattr(e, 'request'): 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()}") raise # Пробрасываем таймаут наверх except Exception as e: