Железобетонные мьютексы в PHP

Alek$ ср, 13/10/2020 - 18:56

Многопоточное программирование, оно такое...Я хочу рассказать об одном нестандартном применении механизма сессий в PHP. Вспомнилось мне это в связи с позавчерашним постом Тормоза на тему, что опять в Даосе проблема с параллельным доступом к файлам - функция блокировки дала очередной сбой. И хотя Тормоз в комментах уже писал, что обкатывает исправленный алгоритм, успешно выдерживающий стресс-тест, я все же поделюсь своим решением. Сразу оговорюсь, все нижеизложенное было проделано just for fun и имеет свои недостатки. Зато и работает практически безотказно.

Перейду к технической части. По умолчанию механизм сессий в PHP использует хранилище данных в файлах - каждая сессия лежит в своем файле. При этом, чтобы обеспечить целостность данных используется нехитрый подход эксклюзивной блокировки доступа к файлу. Иными словами, в момент, когда мы делаем session_start(), движок PHP захватывает файл с требуемой сессией и отпускает ее только в момент явного вызова session_write_close() или завершения скрипта. И если в то время, пока мы работаем с сессией в рамках одного запроса, произойдет еще один запрос, использующий ту же сессию, то он заблокируется на session_start() до тех пор, пока первый не освободит файл. Обычно разные клиенты работают в разных сессиях и проблемы не возникает, однако (немного огрубляю) у одного и того же клиента в один момент времени исполняется только один запрос.

Реально проблема встает, когда во время выполнения некоторого длительного запроса (стримминг потока или long polling) надо обеспечить клиенту возможность посылать и другие запросы (ходить по страничкам) и вовремя получать ответы. Решают задаю обычно одним из трех путей:

  1. Реализуют полностью свой механизм сессий.
  2. Переопределяют обработчики операций с данными сессии во встроенном механизме (с помощью session_set_save_handler(), этим вариантом я в свое время и воспользовался).
  3. Как можно раньше освобождают сессию с помощью session_write_close().

Но я отвлекся. В нашем случае мы не только не будем избавляться от этого, но наоборот будем использовать для реализации мьютекса: будем создавать сессию с заранее известным session_id, к примеру db_mutex, выполнять критические действия и закрывать сессию. Именно эту функциональность и реализует мой класс PHP_Mutex.

Надо признать, такой подход имеет ряд недостатков по сравнению с полноценными мьютексами:

  1. Захватить можно не более одного мьютекса в одно и то же время.
  2. Механизм хранения сессий не должен быть переопределен.
  3. Если вы при этом используете стандартный механизм сессий, возможны потери данных сессии, поскольку на время использования мьютекса блокировка на файл основной сессии снимается.

Но если все условия удовлетворены (кстати, класс по мере возможности сам отслеживает потенциально проблемные ситуации и выдает ворнинги), то блокировка будет железной - разработчики PHP постарались, чтобы данные сессии были в безопасности в любой среде окружения :) Поэтому класс вполне сгодится в небольших скриптах типа Даоса, где сессии не очень нужны. Ну а в сложных системах можно прибегнуть и к тяжелой артиллерии вроде встроенных семафоров, у которых лишь один недостаток - по умолчанию они отключены.

Сам класс вы найдете во вложении, распространяется он свободно согласно лицензии New BSD License. Если кому пригодится - на здоровье :-)

PS. Arsis - A Diamond For Disease



Trackback URL for this post:

/trackback/399

De grootste verzameling kortingscodes op internet

Veel kortings codes om te besparen bij alle nederlandse online winkels

Прикрепленный файл Размер
mutex.php 3.08 кб
Тормоз ср, 13/10/2020 - 22:48

Прикольно, новое слово узнал - mutex :) Надо почитать.
Спасибо. Стресс-тесты провести с использованием твоей штуки интересно, конечно, но я уже боюсь за свой винт, честно говоря.

Alek$ ср, 13/10/2020 - 23:19

Откровенно говоря, мне делать стресс-тесты лень, но может если будет сильно нечем заняться ;-)
Или ты можешь написать тест, а я помучаю свой винт :-D

Тормоз ср, 13/10/2020 - 23:21

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

Alek$ ср, 13/10/2020 - 23:37

Я понимаю. Но это не проблема.

Тормоз чт, 14/10/2020 - 02:58

Спасибо. Результаты будут более показательными, если прогнать аналогичные тесты сразу на двух машинах.

Тормоз чт, 14/10/2020 - 02:57

Хорошо, я постараюсь сделать функции с твоим вариантом и вышлю тебе тогда три дистрибутива Daos с разной реализацией + Bash скриптик для запуска. Но это завтра, не раньше. Сейчас уже в сон падаю, хотя ещё нужно подготовить дистр с вариантом от Jungle и запустить тест на ночь.

Тормоз пт, 15/10/2020 - 02:48

Погоди. Что-то или я не понял, или ты не понял задачу. А как эта штука будет себя вести, если файл запрашивают несколько РАЗНЫХ пользователей? Именно этот случай наиболее критичный.

Тормоз пт, 15/10/2020 - 04:18

Уфф, кажется понял, идентификатор ведь один. Ты зря у класса не описываешь интерейс, это было бы гораздо более полезно читать, чем BSD-лицензию :) Непростая штука, нетривиальная. Сейчас попробую применить.

Alek$ пт, 15/10/2020 - 12:01

У обоих публичных методов есть описание в стандартных блоках phpDoc - /** */

Тормоз пт, 15/10/2020 - 04:29

Хм. Почему-то у меня нельзя сессии писать:

Warning: session_start() [function.session-start]: open(/var/lib/php/sess_test, O_RDWR) failed: Permission denied (13) in /home/me/www/test/classMutex.php on line 74
PHP Warning: Unknown: open(/var/lib/php/sess_test, O_RDWR) failed: Permission denied (13) in Unknown on line 0
Warning: Unknown: open(/var/lib/php/sess_test, O_RDWR) failed: Permission denied (13) in Unknown on line 0
PHP Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/var/lib/php) in Unknown on line 0
Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/var/lib/php) in Unknown on line 0

Сейчас узнаю, как эту хрень включить.

Тормоз пт, 15/10/2020 - 04:33

Ага, session_save_path рулит ) Буду писать их в тот же каталог, где сами файлики.

Тормоз пт, 15/10/2020 - 04:35

А ты сам тестировал?
Notice: Use of undefined constant E_USER_FATAL - assumed 'E_USER_FATAL' in /home/me/www/test/classMutex.php on line 69

Warning: trigger_error() expects parameter 2 to be long, string given in /home/me/www/test/classMutex.php on line 69

Alek$ пт, 15/10/2020 - 12:10
Откровенно говоря, не особо тщательно :-) Поправил.


Отправить комментарий

CAPTCHA
Вы точно не бот?
13 + 7 =
Without JavaScript you won't pass captcha test, sorry. Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.