Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to perform lock file (for having critical section purpose) cleanup

By referring to flock(): removing locked file without race condition? and Will flock'ed file be unlocked when the process die unexpectedly? ,

I produce the following code. My intention is to allow only single thread / single process to run the critical section code, within any given time.

<?php

// Exclusive locking based on function parameters.
$lockFileName = '/tmp/cheok.lock';
// Create if doesn't exist.
$lockFile = fopen($lockFileName, "w+");

if (!flock($lockFile, LOCK_EX)) {
    throw new \RumtimeException("Fail to perform flock on $lockFileName");
}

echo "start critical section...\n";
sleep(10);
echo "end critical section.\n";

// Warning: unlink(/tmp/cheok.lock): No such file or directory in
unlink($lockFileName);
flock($lockFile, LOCK_UN);

I will always get the warning

Warning: unlink(/tmp/cheok.lock): No such file or directory in

when the 2nd waiting process continues his execution, the 1st process already remove the physical disk file. The 2nd process tries to unlink file, which is already deleted by the 1st process.

And, what if, there is 3rd process join in, and tries the perform fopen while 2nd process is trying to perform unlink?

In short, what is the correct way to perform lock file cleanup?

like image 909
Cheok Yan Cheng Avatar asked Oct 20 '22 01:10

Cheok Yan Cheng


1 Answers

The question is tagged with multithreading, so I'm going to answer the question in the context of multi-threading with pthreads.

First, a little bit about file locking. The PHP manual fails to fully explain what an advisory lock is.

Taken from the man page for flock

flock() places advisory locks only; given suitable permissions on a file, a process is free to ignore the use of flock() and perform I/O on the file.

In a ecosystem filled with people who simultaneously don't know much about permissions, and use cron jobs to execute long running scripts, this can be the cause of much pain ... although nobody really knows it.

In the context of multithreading (with pthreads), you want to stay well away from file locking, pthreads provides a much superior API for implementing mutual exclusion.

Follows is example code that creates a number of Threads, and implements mutual exclusion:

<?php
class Test extends Thread {

    public function __construct(Threaded $monitor) {
        $this->monitor = $monitor;
    }

    public function run () {
        $this->monitor->synchronized(function(){
            for ($i = 0; $i < 1000; $i++)
                printf("%s #%lu: %d\n",
                    __CLASS__, $this->getThreadId(), $i);
        });
    }

    private $monitor;
}

$threads = [];
$monitor = new Threaded();
for ($i = 0; $i < 8; $i++) {
    $threads[$i] = new Test($monitor);
    $threads[$i]->start();
}

foreach ($threads as $thread) 
    $thread->join();

Because all the Threads share the same $monitor, you can use the synchronization built into Threaded objects to implement reliable mutual exclusion, causing the output to resemble:

Test #140561163798272: 0
<-- snip for brevity -->
Test #140561163798272: 999
Test #140561151424256: 0
<-- snip for brevity -->
Test #140561151424256: 999
Test #140561138841344: 0
<-- snip for brevity -->
Test #140561138841344: 999
Test #140561059149568: 0
<-- snip for brevity -->
Test #140561059149568: 999
Test #140561050756864: 0
<-- snip for brevity -->
Test #140561050756864: 999
Test #140561042364160: 0
<-- snip for brevity -->
Test #140561042364160: 999
Test #140561033971456: 0
<-- snip for brevity -->
Test #140561033971456: 999
Test #140561025578752: 0
<-- snip for brevity -->
Test #140561025578752: 999

We can see that each Thread is mutually excluded from executing the code in the Closure passed to Threaded::synchronized.

like image 178
Joe Watkins Avatar answered Oct 29 '22 16:10

Joe Watkins