Самый надежный и безопасный метод предотвращения гонки в PHP - PullRequest
20 голосов
/ 15 февраля 2012

Мне нужно использовать мьютексы или семафоры в PHP, и это меня пугает.Чтобы уточнить, я не боюсь писать код без взаимоблокировок, который синхронизируется должным образом, или боится рисков параллельного программирования, а также того, насколько хорошо PHP обрабатывает незначительные случаи.

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

Я запускаю PHP за nginx через PHP-FPM с несколькими процессами на многоядерной машине Linux.Я хочу быть уверен, что семафоры

  1. совместно используются всеми процессами php-fpm и всеми ядрами (ядром i686).
  2. php-fpm обрабатывает сбой процесса PHP при удержанииmutex / семафор и освобождает его соответственно.
  3. php-fpm обрабатывает прерывание сеанса, удерживая мьютекс / семафор, и освобождает его соответственно.

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

Что касается (1), я предполагаю, что если PHP использует функции POSIX, оба эти условия выполняются на машине SMP i686.Что касается (2), из краткого просмотра документов я вижу, что есть параметр, который определяет это поведение (хотя почему бы PHP никогда не освобождать мьютекс, если сеанс уничтожен, я не понимаю).Но (3) это моя главная проблема, и я не знаю, можно ли предположить, что php-fpm правильно обрабатывает все дополнительные случаи для меня.Я (очевидно) никогда не хочу тупиков, но я не уверен, что могу доверять PHP, чтобы он никогда не оставлял мой код в состоянии, когда он не может получить мьютекс, потому что сеанс, который захватил его, был либо изящно, либо изящно завершен.

Я рассмотрел использование подхода MySQL LOCK TABLES, но здесь есть еще больше сомнений, потому что, хотя я доверяю блокировке MySQL больше, чем блокировке PHP, я боюсь, что PHP прерывает запрос (с * out * crash), покаудерживая блокировку сеанса MySQL, MySQL может держать таблицу заблокированной (особенно потому, что я легко могу представить код, который может вызвать это).

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

У кого-нибудь есть какой-либо лучший связанный с параллелизмомпрактики, касающиеся PHP, которыми они хотели бы поделиться?

Ответы [ 5 ]

9 голосов
/ 15 февраля 2012

На самом деле, я думаю, что нет необходимости в сложном мьютексе / семафоре, какое бы решение ни было.

Прочитав ответ dqhendricks и проведя некоторые исследования, я обнаружил, что ключи формы через $_SESSION - это все, что вам нужно,В PHP сеансы заблокированы и session_start() ждет, пока пользовательский сеанс не будет освобожден.Вам просто нужно unset() ключ формы в первом действительном запросе.Второй запрос должен ждать, пока первый не освободит сеанс.

Однако при выполнении в сценарии балансировки нагрузки (не на основе сеанса или исходного IP) все усложняется.Для такого сценария, я уверен, вы найдете ценное решение в этой замечательной статье: http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/

Я воспроизвел ваш вариант использования со следующей демонстрацией.просто закиньте этот файл на ваш веб-сервер и протестируйте:

<?php
session_start();
if (isset($_REQUEST['do_stuff'])) {
  // do stuff
  if ($_REQUEST['uniquehash'] == $_SESSION['uniquehash']) {
    echo "valid, doing stuff now ... "; flush();
    // delete formkey from session
    unset($_SESSION['uniquehash']);
    // release session early - after committing the session data is read-only
    session_write_close();
    sleep(20);  
    echo "stuff done!";
  }
  else {
    echo "nope, {$_REQUEST['uniquehash']} is invalid.";
  }     
}
else {
  // show form with formkey
  $_SESSION['uniquehash'] = md5("foo".microtime().rand(1,999999));
?>
<html>
<head><title>session race condition example</title></head>
<body>
  <form method="POST">
    <input type="hidden" name="PHPSESSID" value="<?=session_id()?>">
    <input type="text" name="uniquehash" 
      value="<?= $_SESSION['uniquehash'] ?>">
    <input type="submit" name="do_stuff" value="Do stuff!">
  </form>
</body>
</html>
<?php } ?>
1 голос
/ 15 февраля 2012

У вас интересный вопрос, но у вас нет данных или кода для показа.

В 80% случаев шансы на что-нибудь неприятное из-за самого PHP практически равны нулю, если вы будете следовать стандартным процедурам и практикам, которые запрещают пользователям отправлять формы несколько раз, что применимо практически ко всем другим настройкам, а не только к PHP.

Если вы на 20% и ваша среда требует этого, то один из вариантов - использовать очереди сообщений, с которыми, я уверен, вы знакомы. Опять же, эта идея не зависит от языка. Ничего общего с языками. Это все о том, как данные перемещаются.

1 голос
/ 15 февраля 2012

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

это должно предотвратить как повторные отправки формы, так ипомочь предотвратить атаки csrf.

0 голосов
/ 22 декабря 2017

Что я делаю для того, чтобы предотвратить состояние гонки сеанса в коде, после последней операции, которая сохраняет данные в сеансе, я использую функцию PHP session_write_close () обратите внимание, что если вы используете PHP 7, вам нужно отключить буферизацию вывода по умолчанию в php.ini. Если у вас есть трудоемкие операции, было бы лучше выполнить их после вызова session_write_close ().

Надеюсь, это кому-нибудь поможет, для меня это спасло мою жизнь :)

0 голосов
/ 15 февраля 2012

Если проблема возникает только при нажатии кнопки на расстоянии в миллисекунды, разве не будет работать программный дебоунсер?Как сэкономить время нажатия кнопки в переменной сеанса и не допустить, скажем, секунды?Просто идея кофе до моего утра.Приветствия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...