Зависание скриптов из-за блокировки сессий в PHP

Одна из самых странных «багофич» PHP выражается тем, что при определённых условиях становится невозможным запуск одного и того же скрипта (или даже разных скриптов) одновременно несколько раз на одном и том же сервере.

Все более-менее опытные программисты знают, что в PHP есть встроенный механизм хранения данных между запросами, который называется «сессиями».

Суть оного (если кто не знает) находится под спойлером.

Открыть

Суть сессий заключается в том, что по окончании выполнения скрипта всё содержимое переменной $_SESSION автоматически сериализуется (упаковывается в строку) и сохраняется в специальный файл во временной папке. При следующем запросе этот файл автоматически считывается, десериализуется и полученное содержимое записывается в переменную $_SESSION.

Таким образом, у скрипта складывается впечатление, что всё записанное в переменную $_SESSION живёт «вечно», то есть не пропадает бесследно при завершении скрипта (в отличие от обычных переменных).

Имя временного файла, в котором будет сохранено содержимое переменной $_SESSION, определяется так называемым идентификатором сессии или session_id, или SID. Чтобы при следующем запросе успешно считать обратно те же самые данные, идентификатор сессии должен быть где-то сохранён и передан в следующем запросе. Обычно для этого используется кука или же дополнительная переменная в запросе GET (это выглядит как дополнение вроде ?SID=ab87d9f98e09da в URL), в запросе POST или же в специальной куке с именем SID.

Баг (а точнее, особенность) стандартных сессий PHP заключается в том, что файл, в котором хранятся данные сессии, на время работы скрипта блокируется для доступа. Это делается не случайно, а умышленно, чтобы в результате перезаписи файла каким-то другим скриптом его данные не потерялись.

А на практике это выражается тем, что физически невозможно отправить на одновременную обработку два скрипта с одним и тем же идентификатором сессии. Как только один скрипт запустился и открыл сессию (с помощью session_start() или же сессия открылась автоматически — есть опция в php.ini), так сразу эта сессия становится недоступной для других скриптов (причём это может быть вторая копия этого же скрипта) и при попытке открыть сессию они просто зависают на неопределённое время, а именно — до того момента как завершится первый скрипт или произойдёт таймаут.

Такой же точно эффект может проявляться в том случае, если из одного скрипта вы пытаетесь делать вызов другого скрипта этого же сайта. В этом случае вызванный скрипт натыкается на блокировку доступа к сессии и зависает на строке session_start(), а первый скрипт, вызвавший его, тоже зависает, поскольку ждёт ответа от второго, вызванного, скрипта.

Вот такие дела.

И теперь, когда вы знаете в чём заключается проблема, вы наверняка уже поняли, как обходить подобные ситуации. Ну а для тех, кто не догадался, я привожу ряд решений, или даже правильнее сказать, правил, как следует поступать, чтобы не попасть в ловушку.

Правило номер 1.

Не используйте сессии, если в этом нет необходимости. Например, если скрипт предназначен для запуска из cron, в нём совершенно не обязательно открывать сессию через session_start(), поскольку вы всё равно не сможете передать куку, содержащую SID.

Правило номер 2.

Делайте скрипты, использующие сессию, максимально шустрыми и простыми. Идея заключается в том, чтобы у пользователя не возникло желания нажать другую ссылку, пока открывается первая. Если он это успеет сделать, то второй скрипт зависнет в ожидании окончания выполнения первого скрипта и пользователю придётся ждать ещё дольше, пока он в конце концов не покинет сайт.

Правило номер 3.

Старайтесь не вызывать из своих скриптов страниц своего же сайта. Я имею ввиду вещи вроде

$str = file_get_contents('http://myownsite.ru/mydatapage.html');

Или же, если это необходимо, закрывайте сессию перед вызовом, а затем снова её открывайте. Примерно вот так:

session_write_close();

$str = file_get_contents('http://myownsite.ru/data.php');

session_start();

Правило номер 4.

PHP позволяет переопределять механизм хранения сессий на свой собственный. Например, можно хранить данные не в файле, а в базе данных или даже в оперативной памяти (используя memcached). В ближайших статьях я об этом напишу.

Подписывайтесь на мой блог и будьте в курсе!
[subscribe2]

Подписаться
Уведомить о
guest

 

5 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
ErickSkrauch

А где же примеры?

JILeXanDR

Полезная статья! Была проблема из зависанием сайта через подобное. На одной странице (разположен вызов сессии) выполнялся тяжелый скрипт, и все… И не мог понять в чем пролема, почему все виснет намертво!

Chernyy Mikhail

Помогите пожалуйста решить данную проблему. Суть в том, что ответ сервера выдает дату в прошлом:
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: max-age=604800, must-revalidate
Pragma: no-cache
На моём сайте установлен скрипт jcart и именно из-за его работы сервер выдает такой ответ. Незнаю возможно ли перекрыть какими-то другими средствами выдачу по-умолчанию дату в прошлом. т.к. никакие записи в .access файлах и httpd.conf и php.ini не дают никакого результата. Если нет, то как в самом скрипте решить эту проблему? Оплачу за помощь. Спасибо.
artchern.com