Benjamin Schulte
Benjamin Schulte

Reputation: 873

How does PHP manage simultaneously requests

Today I just was wondering about how PHP handles simultaneously requests. As PHP can process multiple requests at the same time, I thought about possible security holes or bugs in PHP scripts and I just wanted to know if I'm just a bit too scared about.

So, in the case there are 100 requests at the same time and apache is configured to forward them to PHP. What will PHP do with the following examples (All examples I already saw in some real world applications in some way like this)

All examples are similar to each other. (I don't ask for better ways to solve those example cases)


Example 1: Create a cache

<?php
if (!file_exists('my_cache.txt')) {
    // do something slow (taking a second or so)
    file_put_contents('my_cache.txt', $cache);
}

Assuming we have about 100 requests. Isn't it possible that the cache is generated 100 times and stored 100 times in the cache file?


Example 2: Write an entry to a cache

<?php
writeItemToDatabase($myItem);

if (countAllItemsInDatabase() > 100) {
    $items = readAllItemsFromDatabase();
    deleteAllItemsFromDatabase();
    // Process items
}

This example is a little bit stupid because of the "deleteAllItemsFromDatabase" function. If this script would be executed parallel it could happen that:


Example 3: Virtual money

<?php
if ($user->getMoney() > 100) {
    $user->decreaseMoney(100);
    $user->addItem($itemToBuy);
}

This example has a big security issue if scripts may run simulaneously. If I hit the "buy" button of this application quickly, I might be able to buy an item, even if there is no money left on my users account.


The question

I'm wondering if I'm just a bit paranoid about writing scripts to prevent such problems or are these example real problems?

And - for the rare case - if I need to write some action processed serial (like in those example), is there a PHP function/extension to ensure being processing a script part only once at a time, like:

<?php
$semaphore->lock();
// Do something dangerous
$semaphore->unlock();

Upvotes: 4

Views: 999

Answers (4)

mpapec
mpapec

Reputation: 50637

https://github.com/mpapec/simple-cache/blob/master/example3.php

require "SafeCache.class.php";


// get non blocking exclusive lock
$safe = new SafeCache("exclusive_lock_id");

if ( $safe->getExclusive() ) {
  print "we have exclusive lock now<br>";

  // ...

  print "releasing the lock<br>";
  $safe->doneExclusive();
}

Also, look at other examples for safe cache generation. https://github.com/mpapec/simple-cache/blob/master/example1.php

Upvotes: 0

MaxXx1313
MaxXx1313

Reputation: 620

Sorry for my english.

First i'm going to describe some general features:

  1. This is real =)

    $semaphore->lock();

    // Do something dangerous

    $semaphore->unlock();

  2. i'm trying to describe base concept. Code not suitable for release

  3. Semaphore should have some types: file, database and other necessary.
  4. Realization will be different for each type.

First let's make file realization. We will use embedded function flock (thanks to Salman A ).

<?php
$fname = 'test.txt';

$file = fopen($fname, 'a+');
sleep(5); // long operation
if(flock($file,LOCK_EX|LOCK_NB )){// we get file lock - $semaphore->lock();

  sleep(5); // long operation
  fputs($file, "\n".date('d-m-Y H:i:s')); //something dangerous
  echo 'writed';
  flock($file,LOCK_UN ); // release lock - $semaphore->unlock();
}else{
  // file already locked
  echo 'LOCKED';
}
fclose($file);

Second let's make database locking. In general, some databases may have mechanism to lock single table record, in that case you should use that mechanism. But other databases not supported that feature, for example MySql. For that case lets make some magic :)

For example we have simple table

 CREATE TABLE `threading` (
        `id` INT(10) NOT NULL AUTO_INCREMENT,
        `val` INT(10) NOT NULL,
        PRIMARY KEY (`id`)
    )COLLATE='utf8_general_ci'
    ENGINE=InnoDB

Lets add column, which will simulate "lock" record:

ALTER TABLE `threading`
    ADD COLUMN `_lock` BIT NOT NULL AFTER `val`;

Now we can set _lock fild to 1 for locked record!

IMPORTANT: you must take lock by single query like this: update threading set _lock = 1 where id = 1 AND _lock <> 1 ;.
Note: AND _lock <> 1 prevent record from locking when it's already locked, so you can resolve whether record was locked by rows_affected mechanism.

<?php

// connect
mysql_connect('localhost','root','root');
mysql_selectdb('testing');

// get info
$res = mysql_query('select * from threading where id = 1;');
$row = mysql_fetch_assoc($res);

print_r($row); // debug

if($row['val']>=70){
  sleep(5); // emulate long-long operation =)

  // try to lock
  mysql_query('update threading set _lock = 1 where id = 1 AND _lock <> 1 ;'); // _lock <> 1 - very IMPORTANT!
  sleep(5); // emulate long-long operation =)
  $affected_rows = mysql_affected_rows();
  if($affected_rows!=1){  
    // lock failed -  locked by another instance
    echo '<br> LOCKED!';
  }else{
    // lock succeed
    mysql_query('update threading set val = val-70 where id = 1;');//something dangerous

    mysql_query('update threading set _lock = 0 where id = 1;'); // UNLOCK!
  }

}

// view result
$res = mysql_query('select * from threading where id = 1;');
$row2 = mysql_fetch_assoc($res);

echo '<br>';
print_r($row2);

// disconnect
mysql_close();

So, test was very simple - run files in different browsers at same time. For another type of semaphore you should use other logic and features

Upvotes: 0

zavg
zavg

Reputation: 11061

+1 to killer_PL and in addition to his answer:

Memcache cas() or add() functions are very convenient for file locking implementation.

Add() stores variable with certain key only if such key doesn't exist at the server yet. Cas() also performs a "check and set" operation. It is very easy to design semaphore based on one of these operations.

Upvotes: 0

Piotr M&#252;ller
Piotr M&#252;ller

Reputation: 5548

Things wich you consider and code samples are not thread safe. This is not PHP issue, but concurrency general.

The solution is:

  • for file operations like sample 1 and 2, use file locks.

  • for operations like your money transaction, use database transactions or eventually table locks.

As i know, PHP doesn't provide semaphore mechanism. Remebmer that internal implementation of a server, or configuration (like apache prefork/worker) can even spawn every request in another process - so you don't have to worry about shared memory. Worry about resources - files, database etc.

Such semaphores you mention, are not a good solution. For example at database level, db engine can lock/unlock individual tables or even rows, and this is very efficitent comparing to "locking whole server on that piece of code".

Upvotes: 1

Related Questions