0

Situation: I created a cron job. It was to add orders entered by customers to an API. I set it to run after every one minute.

Problem: Sometimes, when there were too many pending orders, second cron job was being called before first one gets ended. It was resulting in duplicate orders sent through API.

Solution: I created a table named as cron_jobs in my database. It has two columns, ID and status. When a cron job runs, it checks whether the status is 0 or not. If status is 0, it updates the status as 1. It completes its process and marks the status again as 0. If the status of a job is 1 (it means it is already running), and it is hit again, the operation is terminated.

Unexpected Trouble: This solution seems logically correct. But when I implemented it, the results were shocking. When a job is run, its status is updated. But when it is invoked again, database still returns status 0 and it keeps on executing. I have also used sleep method so that I have enough time to investigate the issue. But the database always returns incorrect value than what I can see using phpmyadmin.

Please do no think that I might have committed some stupid mistakes. Second instance of a file is always fetching incorrect value before the first instance is completely invoked. Have a look at my code:

<?php
$Output = mysql_fetch_assoc(mysql_query("select status from jobs where ID='1'"));
if($Output['status'] == '0')
{
    mysql_fetch_assoc(mysql_query("update jobs set status='1' where ID = '1'"));
    mysql_fetch_assoc(mysql_query("update jobs set invoked = invoked+1 where ID = '1'"));
    echo 'Executed';
}
else
{
    echo 'Error: Another cron job already in process. Operation terminated!';
    die();
}

/*I perform some lengthy tasks here*/

    /*Sleep function called to check whether another instance of the program works while this one is in progress */
    sleep(60);

    /*Task is complete. We are marking 0 as status so that another instance is allowed to work on.*/

    mysql_fetch_assoc(mysql_query("update jobs set status='0' where ID = '1'"));
?>

Observation 1: It works perfectly if I run the second instance in another browser or another computer.

Observation 2: I tried to investigate whether it is ACTUALLY being called several times or not. I made another column in the table as "No_Of_Times_Invoked". And I found that the code actually failed when I used from the same browser.

4

1 回答 1

1

Your SELECT query uses the table cron_jobs. Every other query uses jobs. I believe your SELECT query fails, causing mysql_query() to return false.

$Output = mysql_fetch_assoc(mysql_query("select status from cron_jobs where ID='1'"));

Because mysql_query() returns false here, mysql_fetch_assoc() also returns false.

if($Output['status'] == '0')

$Output is false. In PHP, when using ==, type conversion can take place. In this case, $Output['status'] == '0' evaluates to true. (Note that $Output['status'] is null, but null == '0' evaluates to false. The joys of type juggling and loose comparisons.)

Replace your query with

SELECT `status` FROM jobs WHERE ID = '1'

and it should run OK.

I do recommend to add some more error checking to the return values of the mysql_*() functions (use $returnValue === false) or, even better, to switch to PDO.


Updated after some discussion in the comments

It seems the problem is that you're testing from a browser. At least Firefox 23.0.1 (which I used for testing) does not send the second request until the first request is complete. This would explain all behavior you're seeing. A new browser or computer would not be able to re-use the connection so in that case it works.

If I launch the script from the command line twice (five seconds apart), it works correctly: the second run outputs Error: Another cron job already in process. Operation terminated!.

Note that PHP does complain about the invalid mysql_fetch_assoc() you have around each mysql_query("update ...") function call:

Warning: mysql_fetch_assoc() expects parameter 1 to be resource, boolean given in /test/test.php on line 7

于 2013-08-19T19:49:46.010 回答