Статьи


На днях я столкнулся с очень интересной проблемой. В системе, с которой я разбирался, использовался механизм ограничения времени жизни сессии. Валидация этого времени перекладывалась на плечи 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
1
2 
3 
...

Причина довольно проста и, я бы сказал, очевидна:
gc запустится только в 1 из 1000 запросов, а мы сделали всего 15.


Примечание: большинство из систем, которые я видел, работали по этому алгоритму и глубже не заходили.


Обойти баг любой ценой, или пример №2

Решение проблемы кажется простым — а что если запуск GC сделать принудительным?

<?php 
ini_set("session.gc_maxlifetime", 1);

ini_set("session.gc_divisor", 1);
ini_set("session.gc_probability", 1);

session_start(); 
if (isset($_SESSION['value'])) {
$_SESSION['value'] += 1
} else {
$_SESSION['value'] = 0; }

echo $_SESSION['value'];
?>

Но поведение этого скрипта становится намного более неожиданным. Давайте попробуем повторить такие же действия, что и для примера №1:

0 
1 
0 
1 
...

Разбор полетов, или почему так происходит

Если мы повесим обработчики, с помощью session_set_save_handler, то с легкостью восстановим порядок загрузки/обработки сессии:

  1. open

  2. read

  3. gc

  4. PROGRAM

  5. close

Т.е. garbage collector запустился уже после чтения сессии, а значит массив $_SESSION уже заполнен. Вот отсюда и возникает неожиданная единица во втором примере!


Вернемся к 1ому примеру

Как мы теперь видим, сборщик мусора может запустится на 3ем шаге, но что же произойдет если он не запустится? Ведь при стандартных настройках шанс на запуск всего 1 из 1000.
Устаревшая сессия успешно откроется, прочитается, а в конце работы сохранится и время последнего обращения к файлу будет обновлено — в этом случае такая сессия становится почти бесконечной. Но, в тоже время, если наш скрипт использует 1000 разных пользователей, то о «бесконечности» сессии можно забыть, т.к. GC скорее всего запустится у кого либо из пользователей, время жизни начнет работать верно(точнее почти верно). Такое поведение системы неоднозначно и непредсказуемо, а это потенциально приведет к большому количеству трудно отлавливаемых проблем.


И что теперь делать

или выходы из ситуации Самым верным решением, является использования своего механизма валидации сессии. В документации явно сказано что «session.gc_maxlifetime задает отсрочку времени в секундах, после которой данные будут рассматриваться как „мусор“ и потенциально будут удалены. Сбор мусора может произойти в течение старта сессии (в зависимости от значений session.gc_probability и session.gc_divisor).» Слова «потенциально» и «может», как раз и говорят о том, что gc не предназначен для ограничения времени жизни сессии. В тех местах, где время жизни сессии важно, а возникновение артефактов, как из примера №2 критично, используйте свою валидацию времени жизни.


Выход №2, плохой и неправильный
Мы знаем, что установленный «принудительный режим» работы gc отработает на шаге №3 старта сессии. Т.е. фактически после старта устаревшей сессии данные в массиве $_SESSION присутствуют, а файл уже удален. В таком случае логично попробовать пересоздать сессию, т.е фактически сделать запуск 2 запуска session_start:

<?php 
ini_set("session.gc_maxlifetime", 1); 

ini_set("session.gc_divisor", 1); 
ini_set("session.gc_probability", 1); 

session_start(); 
if (isset($_SESSION['value'])) {
$_SESSION['value'] += 1
} else {
$_SESSION['value'] = 0

echo $_SESSION['value']; 
session_commit(); session_start(); 
echo ' '.$_SESSION['value']; 
?>

Результаты работы скрипта будут:

0 0 
1 
0 0 
1 
...

Это поведение ясно из порядка обработки сессии, но(вспомним документацию, да и вообще взглянем адекватно) делать так не стоит.


Ура, разобрались — вывод

Меня удивило, что большинство, даже опытных, разработчиков ни разу не задумывались о поведении GC, беззаботно доверяя ему ограничение времени жизни сессии. При том что в документации явно указано, что делать этого не стоит, а название Garbage Collector(не Session Validator, или Session Expire) говорит само за себя. Ну а главный вывод, конечно, заключается в том, что следует тщательно проверять, даже кажущиеся очевидными части системы. Ошибки системных функций или методов иногда являются их неверной трактовкой, а не ошибками как таковыми.



Очистка папки mod-tmp

и проблемы с Debian + ispmanager


Есть такая интересная штука. На системах с Debian c установленной панелью управления ispmanager lite не удаляются файлы сессий. Через несколько месяцев ваш сервер благополучно ложится. Потом оказывается, что папка с сессиями занимает пару десятков гигабайт. Знакомая ситуация? А происходит это потому, что панель управления меняет session.save_path. Решение хоть и простое, но не быстрое.


  1. Переименовываем папку mod_tmp в mod_tmp_old. Для новых сессий создаем новую папку mod_tmp

  2. Чистим папку mod_tmp_old командой:
    find tmp/ -type f -mmin +360 -delete
    где tmp/ — путь к папке со старыми сессиями, а +360 время за которое удалять сессии. Например при таком значении, будут удалены все файлы сессий, время создания которых более 6 часов.
    Приготовьтесь к тому, что файлы будут удаляться несколько дней!

  3. Исправляем проблему с неудалением сессий.
    В конфиг файле /etc/php.d/apache/php.ini
    Ставим session.gc_probability=1
    По умолчанию стоит 0
    Перезагружаем apache
    /etc/init.d/apache2 restart
    Вот и все :)


Источник: habrahabr.ru и svirchoff.ru



Возврат к списку


Текст сообщения*
Защита от автоматических сообщений
Облако тегов
API array CSS facebook IT-биографии JavaScript jQuery Microsoft microsoft MySQL PC php seo SQL ssl twitter апокалипсис баг база данных бизнес битрикс Битрикс браузер веб-ресурс векторная графика графика дизайн единорог жены программистов звук ЗОЖ инстаграмм интернет-магазин инфографика искусство истории ит картинки клиент компьютер конец света конференция кроссбраузерность лень массивы метод Верле музыка мысли мышь объявления ОС отдых отцы и дети парнас ай ти парнас айти передача данных подростки посмеяться правописание причины лени программирование программист продвижение проект размер страницы разметка разработка разработка интернет-магазина разработчик веб-систем распознавание звука реклама русский язык сайт семантическая разметка семинар сертификат сисадмин скорость загрузки сайта слушать создание сортировка социальные сети творчество тестирование умные мысли цитаты яндекс
Последние комментарии

Наши клиенты

Контакты

ООО "Парнас"

420111
г. Казань, ул. Пушкина 18

Телефон :
8-843-236-6001
8-499-550-6001

Почта :
mail@parnas-it.com


скачать реквизиты

Политика в отношении обработки персональных данных 0+ © 2012-2021 "Парнас-АйТи"