Опис проблеми
При використанні MadelineProto в Laravel додатках для роботи з Telegram API може виникати критична проблема з накопиченням IPC (Inter-Process Communication) процесів. Це призводить до досягнення системного ліміту файлових дескрипторів та краху додатку.
Симптоми проблеми
|
1 2 3 |
You have reached the limits of stream_select(). It has a FD_SETSIZE of 1024, but you have file descriptors numbered at least as high as 1024. |
|
1 2 3 |
Sending on the channel failed. Did the context die? The channel was already closed! |
Діагностика проблеми
1. Перевірка кількості процесів MadelineProto
|
1 2 3 4 5 6 |
# В Docker контейнері docker exec laravel-cron ps aux | grep madeline | wc -l # На сервері ps aux | grep madeline | wc -l |
Нормально: 1-3 процеси
Проблема: 50+ процесів
2. Перевірка файлових дескрипторів
|
1 2 3 4 5 6 |
# Поточний ліміт ulimit -n # Кількість відкритих файлів процесом PHP lsof | grep php | wc -l |
3. Аналіз логів Laravel
|
1 2 3 4 5 6 |
# Пошук помилок з каналами docker exec laravel-cron grep -i "channel\|context die" /var/www/telegram-api/src/storage/logs/laravel.log # Перевірка помилок з правами доступу docker exec laravel-cron grep -i "permission denied" /var/www/telegram-api/src/storage/logs/laravel.log |
Корінь проблеми
1. Створення нових клієнтів без кешування
Проблемний код:
|
1 2 3 4 5 6 |
public function getClient(int $userId): API { $sessionPath = $this->getSessionPath($userId); return new API($sessionPath, $this->settings); // Новий процес кожного разу! } |
2. Помилки з правами доступу до файлів логів
|
1 2 3 |
fopen(/var/www/telegram-api/src/storage/logs/madeline-2025-09-16.log): Failed to open stream: Permission denied |
3. Неправильне закриття IPC з’єднань
Покрокове рішення
Крок 1: Виправлення прав доступу
|
1 2 3 4 5 6 7 8 9 |
# Створення директорії логів з правильними правами docker exec laravel-cron mkdir -p /var/www/telegram-api/src/storage/logs docker exec laravel-cron chmod 777 /var/www/telegram-api/src/storage/logs docker exec laravel-cron chown -R www-data:www-data /var/www/telegram-api/src/storage # Видалення lock файлів docker exec laravel-cron rm -f /var/www/telegram-api/src/storage/app/telegram/sessions/*.lock docker exec laravel-cron rm -f /var/www/telegram-api/src/storage/app/telegram/sessions/*/*.lock |
Крок 2: Оптимізація TelegramSessionManager
Додавання кешування клієнтів:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class TelegramSessionManager implements SessionManagerInterface { // Кеш клієнтів для уникнення створення дублікатів protected static array $clients = []; protected static int $maxClients = 5; public function getClient(int $userId): API { $clientKey = "user_{$userId}"; // Перевіряємо чи є клієнт в кеші if (isset(self::$clients[$clientKey])) { Log::info('Returning cached client', ['user_id' => $userId]); return self::$clients[$clientKey]['client']; } // Перевіряємо ліміт клієнтів if (count(self::$clients) >= self::$maxClients) { $this->cleanupOldestClients(); } // Створюємо новий клієнт тільки якщо потрібно $sessionPath = $this->getSessionPath($userId); $client = new API($sessionPath, $this->settings); // Зберігаємо в кеші self::$clients[$clientKey] = [ 'client' => $client, 'created_at' => time(), 'user_id' => $userId ]; return $client; } protected function cleanupOldestClients(): void { // Сортуємо за часом створення uasort(self::$clients, function($a, $b) { return $a['created_at'] - $b['created_at']; }); // Видаляємо половину найстаріших $toRemove = max(1, count(self::$clients) / 2); $removed = 0; foreach (self::$clients as $key => $clientData) { if ($removed >= $toRemove) break; try { if (method_exists($clientData['client'], 'stop')) { $clientData['client']->stop(); } } catch (\Exception $e) { // Ігноруємо помилки при закритті } unset(self::$clients[$key]); $removed++; } } } |
Крок 3: Оптимізація обробки повідомлень
Додавання retry логіки та зменшення навантаження:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
private function getNewMessagesForUserBot($client, TelegramUserBots $userBot): array { // Retry логіка для нестабільних з'єднань $maxRetries = 2; $dialogs = null; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { $dialogs = $client->messages->getDialogs(['limit' => 15]); // Зменшений ліміт break; } catch (\Exception $e) { if ($attempt === $maxRetries) { throw $e; } // Відновлення клієнта при помилках каналу if (strpos($e->getMessage(), 'channel') !== false) { $this->removeClient($userBot->id); $client = $this->getOrCreateClient($userBot->id); } sleep(2); } } // Обмеження кількості оброблюваних повідомлень // limit: 5 updates максимум замість 20 } |
Крок 4: Налаштування cron та моніторингу
|
1 2 3 4 5 6 7 8 9 |
# Оптимальна частота запуску - кожні 3-5 хвилин */3 * * * * cd /var/www/telegram-api/src && timeout 240 php artisan userbot:ai-check # Автоматичне очищення завислих процесів */30 * * * * pkill -f "madeline-ipc" -o 300 # Видалення lock файлів 0 */2 * * * rm -f /var/www/telegram-api/src/storage/app/telegram/sessions/*.lock |
Тестування після виправлення
1. Перевірка базової функціональності
|
1 2 3 4 5 6 |
# Тест без реальної обробки docker exec laravel-cron php artisan userbot:ai-check --dry-run # Реальний запуск docker exec laravel-cron php artisan userbot:ai-check |
2. Моніторинг процесів
|
1 2 3 4 |
# До виправлення: 50+ процесів # Після виправлення: 1-3 процеси docker exec laravel-cron ps aux | grep madeline | wc -l |
3. Аналіз логів
Успішна робота (після виправлення):
|
1 2 3 4 |
[2025-09-16 09:45:26] local.INFO: Returning cached client {"user_id":2} [2025-09-16 09:45:26] local.INFO: Dialogs retrieved successfully {"userbot_id":2,"dialogs_count":5} [2025-09-16 09:45:26] local.DEBUG: Message collection completed {"userbot_id":2,"collected_updates":0} |
Проблемні сигнали (до виправлення):
|
1 2 3 |
[2025-09-16 05:45:09] local.ERROR: You have reached the limits of stream_select() [2025-09-16 09:37:55] local.ERROR: Sending on the channel failed. Did the context die? |
Результат
До виправлення:
- 50+ процесів madeline-ipc
- Краші через FD_SETSIZE=1024
- Помилки з каналами IPC
- Нестабільна робота
Після виправлення:
- 1-3 процеси максимум
- Стабільна робота без крашів
- Автоматичне відновлення при помилках
- Кешування клієнтів та економія ресурсів
Додаткові рекомендації
1. Системні налаштування
|
1 2 3 4 |
# Збільшення ліміту файлових дескрипторів (якщо потрібно) echo "www-data soft nofile 65536" >> /etc/security/limits.conf echo "www-data hard nofile 65536" >> /etc/security/limits.conf |
2. PHP налаштування
|
1 2 3 4 5 6 |
; php.ini memory_limit = 1024M max_execution_time = 300 ; НЕ забороняйте proc_open - потрібен для MadelineProto disable_functions = exec,passthru,shell_exec,system,popen |
3. Моніторинг
|
1 2 3 4 5 6 7 |
# Скрипт для регулярної перевірки #!/bin/bash echo "=== MadelineProto Health Check ===" echo "Processes: $(docker exec laravel-cron ps aux | grep madeline | wc -l)" echo "Memory: $(docker exec laravel-cron free -h | grep Mem)" echo "Last errors: $(docker exec laravel-cron tail -5 /var/www/telegram-api/src/storage/logs/laravel.log | grep ERROR)" |
Ця проблема є типовою для масштабованих Telegram додатків на MadelineProto і потребує комплексного підходу до управління ресурсами та процесами.