So I have a script for accepting and processing request from another scripts and/or applications. However, one of the task has to be done with my script is assigning each request a unique, sequential "ID" to each of them.
For example, let's says that application A is giving a 1000 request to my script, and in the same time, application B is giving 500 request to my script. I have to give them 1500 unique, sequential number, like 2001~3500 to each of them.
The order between them however, does not matter, so I can give them numbers like this :
#2001 for 1st request from A (henceforth, A1)
#2002 for A2
#2003 for B1
#2004 for A3
#2005 for B2
...and so on...
I've tried creating a file that stores that number and a separated lock file with a function like this :
private function get_last_id()
{
// Check if lock file exists...
while (file_exists("LAST_ID_LOCKED")) {
// Wait a little bit before checking again
usleep(1000);
}
// Create the lock file
touch("LAST_ID_LOCKED");
// Create the ID file for the first time if required
if (!file_exists("LAST_ID_INDICATOR")) {
file_put_contents("LAST_ID_INDICATOR", 0);
}
// Get the last ID
$last_id = file_get_contents("LAST_ID_INDICATOR");
// Update the last ID
file_put_contents("LAST_ID_INDICATOR", $last_id + 1);
// Delete the lock file
unlink("LAST_ID_LOCKED");
return $last_id;
}
This code, however, would create a race condition, where if I send them those 1500 request, the last ID will have quite a number missings, (e.g. only reach 3211 instead of 3500).
I've also tried using flock like this, but to no avail :
private function get_last_id()
{
$f = fopen("LAST_ID_INDICATOR", "rw");
while (true) {
if (flock($f, LOCK_SH)) {
$last_id = fread($f, 8192);
flock($f, LOCK_UN);
fclose($f);
break;
}
usleep($this->config["waiting_time"]);
}
$f = fopen("LAST_ID_INDICATOR", "rw");
while (true) {
if (flock($f, LOCK_SH)) {
$last_id = fread($f, 8192);
$last_id++;
ftruncate($f, 0);
fwrite($f, $last_id);
flock($f, LOCK_UN);
fclose($f);
break;
}
usleep($this->config["waiting_time"]);
}
return $last_id;
}
So, what else can I do to look for a solution for this situation?
Notes: Due to server limitation, I'm limited to PHP 5.2 without something like semaphores and such.
Since no-one seems to be giving an answer, I'll give you a possible solution.
Use the Lamport's Bakery Algorithm as part of your solution.
Edit: The filter lock would work even better if you don't need the order preserved.
Obviously this will have its own challenges implementing but it's worth a try and if you get it right, it might just do the trick for what you want to do.
Since you mentioned semaphores, I assume you know enough knowledge to understand the concept.
This can be found in chapter 2 of "The art of multiprocessor programming".
If you have access to a database whith locking capabilities you can use that. E.g. for MySQL with skeleton PHP code:
Create a table with one row and one column (if you do not want "dual-use" of an already existing table):
$sql = 'CREATE TABLE TABLENAME
(COLUMNNAME INTEGER) ENGINE=MyISAM';
excecuteSql($sql) ...
Create a PHP function to (re)set the counter/Id value:
$sql = 'UPDATE TABLENAME
SET COLUMNNAME
=0';
executeSql($sql); ...
Create a PHP function to get a unique, successive id:
$sql = "SELECT GET_LOCK('numberLock',10)";
executeSql($sql); ...
$sql = 'SELECT * FROM TABLENAME
';
if ($result = mysqli_query($link, $sql)) {
$row = mysqli_fetch_row($result);
$wantedId = $row[0];
// do something with the id ...
mysqli_free_result($result);
}
$sql = 'UPDATE TABLENAME
SET COLUMNNAME
=COLUMNNAME
+1';
executeSql($sql); ...
$sql = "SELECT RELEASE_LOCK('numberLock')";
executeSql($sql); ...