На днях я столкнулся с очень интересной проблемой. В системе, с которой я разбирался, использовался механизм ограничения времени жизни сессии. Валидация этого времени перекладывалась на плечи garbage collector'а, который почему-то её выполнял не совсем добросовестно, а то и вовсе не выполнял. Как оказалось, ошибки эти общераспространенных, по этому о тонкостях работы с GC я и хотел бы рассказать.
В php за работу GC для сессий отвечают 3 параметра: session.gc_probability, session.gc_divisor и session.gc_maxlifetime. Эти параметры говорят о следующем: в gc_probability из gc_divisor запусков session_start запускается GC, который должен очистить сессии со временем последнего обращения больше, чем gc_maxlifetime.
Делаем как все, или пример №1
Попробуем протестировать работу GCна маленьком скрипте:
<?php ini_set("session.gc_maxlifetime", 1);
session_start();
if (isset($_SESSION['value'])) {
$_SESSION['value'] += 1;
} else {
$_SESSION['value'] = 0;
}
echo $_SESSION['value'];
?>
Обновим этот файл 10 раз с промежутком секунд по 10-15(можно и больше, важно чтобы промежуток был выше чем 1 секунда). В результате мы получим «неожиданные ответы»:
0Причина довольно проста и, я бы сказал, очевидна:
gc запустится только в 1 из 1000 запросов, а мы сделали всего 15.
Примечание: большинство из систем, которые я видел, работали по этому алгоритму и глубже не заходили.
Обойти баг любой ценой, или пример №2
Решение проблемы кажется простым — а что если запуск GC сделать принудительным?
<?php$_SESSION['value'] += 1;
} else {
$_SESSION['value'] = 0; }
echo $_SESSION['value'];?>
Но поведение этого скрипта становится намного более неожиданным. Давайте попробуем повторить такие же действия, что и для примера №1:
0Разбор полетов, или почему так происходит
Если мы повесим обработчики, с помощью session_set_save_handler, то с легкостью восстановим порядок загрузки/обработки сессии:
- open
- read
- gc
- PROGRAM
- close
Вернемся к 1ому примеру
Как мы теперь видим, сборщик мусора может запустится на 3ем шаге, но что же произойдет если он не запустится? Ведь при стандартных настройках шанс на запуск всего 1 из 1000.
Устаревшая сессия успешно откроется, прочитается, а в конце работы сохранится и время последнего обращения к файлу будет обновлено — в этом случае такая сессия становится почти бесконечной. Но, в тоже время, если наш скрипт использует 1000 разных пользователей, то о «бесконечности» сессии можно забыть, т.к. GC скорее всего запустится у кого либо из пользователей, время жизни начнет работать верно(точнее почти верно). Такое поведение системы неоднозначно и непредсказуемо, а это потенциально приведет к большому количеству трудно отлавливаемых проблем.
И что теперь делать
или выходы из ситуации Самым верным решением, является использования своего механизма валидации сессии. В документации явно сказано что «session.gc_maxlifetime задает отсрочку времени в секундах, после которой данные будут рассматриваться как „мусор“ и потенциально будут удалены. Сбор мусора может произойти в течение старта сессии (в зависимости от значений session.gc_probability и session.gc_divisor).» Слова «потенциально» и «может», как раз и говорят о том, что gc не предназначен для ограничения времени жизни сессии. В тех местах, где время жизни сессии важно, а возникновение артефактов, как из примера №2 критично, используйте свою валидацию времени жизни.
Выход №2, плохой и неправильный
Мы знаем, что установленный «принудительный режим» работы gc отработает на шаге №3 старта сессии. Т.е. фактически после старта устаревшей сессии данные в массиве $_SESSION присутствуют, а файл уже удален. В таком случае логично попробовать пересоздать сессию, т.е фактически сделать запуск 2 запуска session_start:
} else {
$_SESSION['value'] = 0;}
echo $_SESSION['value'];session_commit(); session_start();echo ' '.$_SESSION['value'];?>
Результаты работы скрипта будут:
0 0Это поведение ясно из порядка обработки сессии, но(вспомним документацию, да и вообще взглянем адекватно) делать так не стоит.
Ура, разобрались — вывод
Меня удивило, что большинство, даже опытных, разработчиков ни разу не задумывались о поведении GC, беззаботно доверяя ему ограничение времени жизни сессии. При том что в документации явно указано, что делать этого не стоит, а название Garbage Collector(не Session Validator, или Session Expire) говорит само за себя. Ну а главный вывод, конечно, заключается в том, что следует тщательно проверять, даже кажущиеся очевидными части системы. Ошибки системных функций или методов иногда являются их неверной трактовкой, а не ошибками как таковыми.
Очистка папки mod-tmp
и проблемы с Debian + ispmanager
Есть такая интересная штука. На системах с Debian c установленной панелью управления ispmanager lite не удаляются файлы сессий. Через несколько месяцев ваш сервер благополучно ложится. Потом оказывается, что папка с сессиями занимает пару десятков гигабайт. Знакомая ситуация? А происходит это потому, что панель управления меняет session.save_path. Решение хоть и простое, но не быстрое.
- Переименовываем папку mod_tmp в mod_tmp_old. Для новых сессий создаем новую папку mod_tmp
- Чистим папку mod_tmp_old командой:
find tmp/ -type f -mmin +360 -delete
где tmp/ — путь к папке со старыми сессиями, а +360 время за которое удалять сессии. Например при таком значении, будут удалены все файлы сессий, время создания которых более 6 часов.
Приготовьтесь к тому, что файлы будут удаляться несколько дней! - Исправляем проблему с неудалением сессий.
В конфиг файле /etc/php.d/apache/php.ini
Ставим session.gc_probability=1
По умолчанию стоит 0
Перезагружаем apache
/etc/init.d/apache2 restart
Вот и все :)
Источник: habrahabr.ru и svirchoff.ru
Наши клиенты
Контакты
ООО "Парнас"
420111
г. Казань, ул. Пушкина 18
Телефон :
8-843-236-6001
8-499-550-6001
Почта :
mail@parnas-it.com