Подтвердить что ты не робот

Самый надежный и безопасный метод предотвращения условий гонки в PHP

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

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

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

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

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

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

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

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

У кого-нибудь есть какие-либо concurrency -рекомендуемые рекомендации по PHP, которые они хотели бы поделиться?

4b9b3361

Ответ 1

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

Ключи формы, хранящиеся в PHP $_SESSION, это все, что вам нужно. В качестве приятного побочного эффекта этот метод также защищает вашу форму от CSRF-атак.

В 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 } ?>

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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