SitePoint Sponsor

User Tag List

Results 1 to 14 of 14
  1. #1
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)

    Is this locking foolproof against race conditions?

    I have created a mechanism that will allow only one instance of a php script to run at the same time - actually the part after "// do your stuff here...". I wonder if it's 100% guaranteed that a race condition does not occur and result in two scripts firing simultaneously:

    PHP Code:
    try {

        
    $lockFile "script.lock";
        
        if (!
    $fp = @fopen($lockFile'x')) {
            
            
    // another instance already running
            
    if (@filemtime($lockFile) < time() - 15*60) {
                
    // after 15 minutes we consider it deadlock, so let's release it
                
    @unlink($lockFile);
                throw new 
    Exception("Deadlock found and realeased. Current script stopped.");
            }

            throw new 
    Exception("Another instance running. Script stopped.");
        }
        
        
    fclose($fp);
        
        
    // do your stuff here that should be done only by 1 script at a time
        // ...
        
        
    @unlink($lockFile);
        
        
    } catch (
    Exception $ex) {
        
    $message $ex->getMessage();

    According to documentation the attempt to create a file in 'x' mode should fail when it already exists so in theory this should work. But can I be sure that the implementation of fopen($file, 'x') is atomic? If in reality there is some C code running first a simple check condition whether the file exists and then creating it if it doesn't then it is possible that this file gets created by two separate threads, the latter overwriting the former. Or is this handled by the file system, which guarantees atomicity?

    I'm mostly interested in how this works on Unix systems. I don't think I am capable of testing this code since it would be extremely difficult for me to start two instances at almost the same time without specialized software, besides I don't work on Unix apart from using Unix hosting. So I can only rely on expertise of others.

  2. #2
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,712
    Mentioned
    102 Post(s)
    Tagged
    4 Thread(s)
    Forgive me for being dense, but I thought that PHP scripts are all executed sequentially, one at a time, and only becomes concurrent under very special circumstances, such as when using the streams wrapper
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  3. #3
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pmw57 View Post
    Forgive me for being dense, but I thought that PHP scripts are all executed sequentially, one at a time, and only becomes concurrent under very special circumstances, such as when using the streams wrapper
    Perhaps you misunderstood my case because I don't think you would lack such basic knowledge about php? Of course, when a php script starts then it gets executed sequentially until it ends but you can have many requests to the same script at the same time and then it gets executed simultaneously in multiple threads. The code above is to prevent multiple simultaneous requests to the same script.

  4. #4
    SitePoint Enthusiast
    Join Date
    Sep 2008
    Posts
    68
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The logic seems ok.
    I'd use the file_exists function instead of trying to open the lock tho. And I don't like hiding errors with @.

  5. #5
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Arkh View Post
    The logic seems ok.
    I'd use the file_exists function instead of trying to open the lock tho. And I don't like hiding errors with @.
    First I used file_exists but later I changed it to fopen with 'x' because with file_exists this would not be reliable. I'd have to do something like this:
    PHP Code:
    if (!file_exists($lockFile)) {
       
    // create lock file
       
    file_put_contents($lockFile'');
       
       
    // do stuff...
    } else {
      
    // another instance running, stop script
      // ...

    And now it's possible that two threads simultaneously decide that the file does not exist and both create the new file. Or, do you have a better idea? fopen() in 'x' mode with error suppression was the best I could think of.

  6. #6
    . shoooo... silver trophy logic_earth's Avatar
    Join Date
    Oct 2005
    Location
    CA
    Posts
    9,013
    Mentioned
    8 Post(s)
    Tagged
    0 Thread(s)
    Is there a reason why you don't use flock....or is there something else going on?
    Logic without the fatal effects.
    All code snippets are licensed under WTFPL.


  7. #7
    SitePoint Wizard TheRedDevil's Avatar
    Join Date
    Sep 2004
    Location
    Norway
    Posts
    1,196
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    I am not sure why your trying to reinvent the wheel.

    Would it not be easier using the available functionality to handle the locking?

    http://bg.php.net/manual/en/function.flock.php

    One thing to keep in mind, the locking method you use need to be used across the system. If you have multiple languages accessing the same file(s) you need to be certain all obey the same locking rules.

    Edit: Guess I should stop opening topics and reading them in between work without refreshing them before replying.

  8. #8
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    My first experience with flock() was quite problematic and besides I wanted a solution that would work cross-platform (at least to some reasonable degree on Windows). And it's not really complicated as compared to using flock(). Anyway, I've done some more testing and flock() seems to work well. And the documentation must be wrong or outdated, because LOCK_NB works on Windows, too - at least on XP with PHP 5.2.4. I have tested this code:
    PHP Code:
    $lockFile "test.lock";

    $fp fopen($lockFile'w+');
    echo 
    "fopen<br/>"ob_flush(); flush();

    if(!
    flock($fpLOCK_EX LOCK_NB)) {
        echo 
    'Unable to obtain lock, script stopped.';
        exit;
    }

    echo 
    "running script after flock...<br/>"ob_flush(); flush();
    sleep(10);

    fclose($fp);
    echo 
    "end."
    Now my question is - in this example is there a possibility of deadlock? Theoretically, the lock should be released when the script ends but what if the script ends unexpectedly due to some server error? Is it possible the lock will never be released then? If so then what can be done about it to handle it automatically?

  9. #9
    SitePoint Zealot
    Join Date
    Dec 2010
    Posts
    187
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just out of curiosity - what exactly are you trying to achieve, for what purpose? There might be another way by creating a simple server or simulating threading rather than relying on file locks.

  10. #10
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    In this specific case I have a script that can be run either by a cron job or as a result of user action - its purpose is to send out email messages queued in the database. I simply don't want the same person to receive multiple messages when more that one instance is running.

    I also want to create a generic method (class) that can be used for similar purposes. There can be other use cases I want to use it for - for example a script that creates a new invoice and there's really a lot other related data that need to be read and changed in an atomic way. Simply using this kind of lock at the beginning of execution to disallow multiple instances may be much easier and much less work than having to deal with locking tables, row locking, transactions, SELECT ... FOR UPDATE and similar methods - it can get quite complicated when there are many tables and data sources involved, and pretty hard to test for all possible edge and extremely rare cases that can happen with multithreading.

  11. #11
    SitePoint Zealot
    Join Date
    Dec 2010
    Posts
    187
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Have you considered creating a simple server that can handle connections to it (sockets, server listening on certain port) and use queueing system to determine what to execute and what to deny? That way you don't have to rely on a simple file being there, or having deadlocks due to some random error.

  12. #12
    SitePoint Guru bronze trophy
    Join Date
    Dec 2003
    Location
    Poland
    Posts
    930
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by furicane View Post
    Have you considered creating a simple server that can handle connections to it (sockets, server listening on certain port) and use queueing system to determine what to execute and what to deny? That way you don't have to rely on a simple file being there, or having deadlocks due to some random error.
    I don't think it is a solution for me - I need something that will be portable and work on a shared hosting. I doubt any shared hosting would implement anything like that for me. This looks like a good solution for really large sites - that would justify the need to setup another server.

  13. #13
    SitePoint Zealot
    Join Date
    Dec 2010
    Posts
    187
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm talking about a simple small server written in PHP, it wouldn't have more than.. well, 20ish lines of code - listening on a port, accepting connections and using queue system to determine what to execute and what to deny.
    But then again, as you said - probably not the way to go since you'd need ssh access and what not :/

  14. #14
    Twitter: @AnthonySterling silver trophy AnthonySterling's Avatar
    Join Date
    Apr 2008
    Location
    North-East, UK.
    Posts
    6,111
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by furicane View Post
    Have you considered creating a simple server that can handle connections to it (sockets, server listening on certain port) and use queueing system to determine what to execute and what to deny? That way you don't have to rely on a simple file being there, or having deadlocks due to some random error.
    I must admit, when I read this thread I too thought a MQ of some sort could quickly remedy this.

    The cool kids are using them these days too, so instant geeky admiration is almost guaranteed.
    @AnthonySterling: I'm a PHP developer, a consultant for oopnorth.com and the organiser of @phpne, a PHP User Group covering the North-East of England.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •