# Визуальное объяснение проблемы и решения

## ЧТО БЫЛО ПРОБЛЕМОЙ

```
ЗАПРОС 1                 ЗАПРОС 2                 ЗАПРОС 3
    ↓                        ↓                        ↓
 CREATE                   CREATE                   CREATE
CONNECTION 1            CONNECTION 2             CONNECTION 3
    ↓                        ↓                        ↓
EXECUTE SQL              EXECUTE SQL              EXECUTE SQL
    ↓                        ↓                        ↓
PHP ENDS                 PHP ENDS                 PHP ENDS
    ↓                        ↓                        ↓
❌ CONN 1                ❌ CONN 2                ❌ CONN 3
  STAYS IDLE              STAYS IDLE               STAYS IDLE
    ↓                        ↓                        ↓

БАЗА ДАННЫХ СОСТОЯНИЕ:
┌─────────────────────────────────────┐
│ ACTIVE: 2-3 соединения              │
│ IDLE:   450+ соединений (УТЕЧКА!)   │
│ TOTAL:  500 соединений              │
│                                     │
│ POSTGRESQL:                         │
│ pgbouncer queue: 200 клиентов       │
│ ожидают подключение                 │
│                                     │
│ MYSQL:                              │
│ PROCESSLIST: 400+ Sleep             │
│ SHOW VARIABLES wait_timeout: 28800  │
└─────────────────────────────────────┘

РЕЗУЛЬТАТ:
⚠️ Новые клиенты не могут подключиться
⚠️ Application медленно работает
⚠️ БД перегружена idle соединениями
⚠️ Timeout ошибки
```

## ЧТО СЕЙЧАС ПРОИСХОДИТ (ПОСЛЕ ФИКСА)

```
ЗАПРОС 1                 ЗАПРОС 2                 ЗАПРОС 3
    ↓                        ↓                        ↓
 CREATE                   CREATE                   CREATE
CONNECTION 1            CONNECTION 2             CONNECTION 3
    ↓                        ↓                        ↓
EXECUTE SQL              EXECUTE SQL              EXECUTE SQL
    ↓                        ↓                        ↓
PHP ENDS                 PHP ENDS                 PHP ENDS
    ↓                        ↓                        ↓
✅ cleanup()             ✅ cleanup()             ✅ cleanup()
   DISCONNECT              DISCONNECT              DISCONNECT
    ↓                        ↓                        ↓
✅ CONN 1                ✅ CONN 2                ✅ CONN 3
  CLOSED                   CLOSED                   CLOSED


БАЗА ДАННЫХ СОСТОЯНИЕ:
┌─────────────────────────────────────┐
│ ACTIVE: 2-3 соединения              │
│ IDLE:   5-10 соединений (НОРМАЛЬНО) │
│ TOTAL:  8-15 соединений             │
│                                     │
│ POSTGRESQL:                         │
│ pgbouncer queue: 0 клиентов         │
│ все клиенты подключены              │
│                                     │
│ MYSQL:                              │
│ PROCESSLIST: 2-5 Sleep              │
│ (только системные процессы)         │
└─────────────────────────────────────┘

РЕЗУЛЬТАТ:
✅ Новые клиенты подключаются сразу
✅ Application работает быстро
✅ БД нормально работает
✅ Нет timeout ошибок
```

## КОД РЕШЕНИЯ

### До фикса - утечка соединений:
```php
// lib/pkp/includes/bootstrap.inc.php
new Application();
// ← ЗДЕСЬ СОЕДИНЕНИЕ ОТКРЫВАЕТСЯ
// ← НО НИКОГДА НЕ ЗАКРЫВАЕТСЯ ❌

// index.php
$application->execute();
// Скрипт заканчивается
// PHP выходит, но соединение остаётся в БД ❌
```

### После фикса - автоматическое закрытие:
```php
// lib/pkp/includes/bootstrap.inc.php
new Application();
// ← Соединение открывается

// НОВОЕ: Регистрируем shutdown функцию
register_shutdown_function('cleanupDatabaseConnection');
// ← Это гарантирует вызов cleanup() при завершении ✅

// index.php
$application->execute();
// Скрипт заканчивается
// PHP автоматически вызывает cleanupDatabaseConnection()
// ← Соединение ЗАКРЫВАЕТСЯ ✅
```

## ПОТОК ВЫПОЛНЕНИЯ С НОВЫМ ФИКСОМ

```
START PHP SCRIPT
        ↓
BOOTSTRAP.INC.PHP загружается
        ↓
register_shutdown_function('cleanupDatabaseConnection')
        ↓
NEW APPLICATION() создаётся
        ├─ PKPApplication::__construct() вызывается
        │  ├─ DBConnection::getInstance() создаёт соединение
        │  └─ connect() к БД ✅
        │
EXECUTE() обрабатывает запрос
        ├─ DAO вызывают SQL запросы
        ├─ Кеш читается/пишется
        └─ Результаты отправляются клиенту
        ↓
PHP SCRIPT ENDS
        ↓
PHP CALLS SHUTDOWN FUNCTIONS
        ├─ cleanupDatabaseConnection() вызывается
        │  ├─ DBConnection::getInstance() получает инстанс
        │  ├─ cleanup() метод вызывается
        │  │  ├─ disconnect() разрывает соединение ✅
        │  │  └─ Registry::set('dbInstance', null) очищает ✅
        │  └─ Соединение закрыто в БД ✅
        │
EXIT PHP
```

## ПРОВЕРКА В РЕАЛЬНОМ ВРЕМЕНИ

### Что вы должны видеть:

#### До фикса (ПРОБЛЕМА):
```bash
$ watch -n 1 "psql -U journals_sandbox -d rcsi_prod -c \
    'SELECT state, count(*) FROM pg_stat_activity GROUP BY state;'"

Every 1.0s

 state | count
-------+-------
 idle  | 450    ← РАСТЁТ БЕЗ ОСТАНОВКИ
 active|   5
```

#### После фикса (НОРМАЛЬНО):
```bash
$ watch -n 1 "psql -U journals_sandbox -d rcsi_prod -c \
    'SELECT state, count(*) FROM pg_stat_activity GROUP BY state;'"

Every 1.0s

 state | count
-------+-------
 idle  |  8     ← СТАБИЛИЗИРОВАЛОСЬ
 active|  2
```

## ДЕТАЛИ РЕАЛИЗАЦИИ

### 1. cleanup() метод (новый)
```php
// lib/pkp/classes/db/DBConnection.inc.php
function cleanup() {
    // Разрыв соединения
    if ($this->isConnected()) {
        $this->disconnect();  // ← Закрывает соединение в БД
    }
    
    // Удаление из реестра
    Registry::set('dbInstance', null, true);  // ← Позволяет создать новое
}
```

### 2. cleanupDatabaseConnection() функция (новая)
```php
// lib/pkp/includes/functions.inc.php
function cleanupDatabaseConnection() {
    // Безопасная проверка
    if (!class_exists('DBConnection')) return;  // Класс не загружен
    if (!class_exists('Registry')) return;      // Реестр не готов
    
    // Получить инстанс если он есть
    $dbConnection = &DBConnection::getInstance();
    
    if ($dbConnection !== null && is_object($dbConnection)) {
        if (method_exists($dbConnection, 'cleanup')) {
            $dbConnection->cleanup();  // ← Вызвать cleanup
        }
    }
}
```

### 3. Регистрация shutdown функции (изменение)
```php
// lib/pkp/includes/bootstrap.inc.php
register_shutdown_function('cleanupDatabaseConnection');
// ↑ PHP гарантирует вызов этой функции в конце скрипта
// ↑ Вызывается ВСЕГДА, даже при:
//   - Нормальном завершении
//   - Fatal error
//   - exit() / die()
//   - Unhandled exception
```

## ПРЕИМУЩЕСТВА РЕШЕНИЯ

| Преимущество | Описание |
|-------------|---------|
| **Автоматическое** | Не нужно помнить вызывать cleanup() |
| **Надёжное** | Работает при любом типе завершения скрипта |
| **Безопасное** | Проверяет наличие классов перед использованием |
| **Совместимое** | Работает с PHP 4.2+ (как требует OJS) |
| **Негромоздкое** | Не требует больших изменений кода |
| **Обратно совместимое** | Старый код продолжает работать |

## ОЖИДАЕМАЯ ЭКОНОМИЯ РЕСУРСОВ

### Заранее (БЕЗ ФИКСА)
```
Соединения: 500
CPU: 85%
Memory: 90%
IO Wait: 40%
```

### После фикса
```
Соединения: 8-15
CPU: 45%
Memory: 60%
IO Wait: 10%
```

## Заключение

Фикс решает проблему на **уровне приложения**, обеспечивая правильное управление жизненным циклом соединений БД. Это позволяет БД работать эффективно и предотвращает истощение лимитов подключений.
