I have a server running on Apache2 inside a Raspberry Pi (Raspbian 11), with php 8.0.
I'm currently writing my own server-side mutex
php module. The idea of the API is to simplify the usage of global mutexes between multiple requests, by using the built-in function: flock
.
This module consists of 2 functions: mutex_lock($name)
and mutex_unlock($name)
. For the sake of simplicity, I will just explain the idea of mutex_lock($name)
.
After some research on global variables, I found out that data stored inside $GLOBALS
persists between sessions. So when a client A loads a resource on the global variable, the same resource should exist in client B's request, given that A's request precedes B's.
Now, back to the module. I use this idea to store file resources so multiple POST/GET requests (also made by AJAX) can access the same file resource and thus the same mutex object.
When mutex_lock($name)
is called:
$GLOBALS['mresources']
must be defined as an associative arrayCheck if
$GLOBALS['mresources'][$name]
is defined:2.1. If it is defined, use this file resource
2.2. If it's not defined, store a new file resource:
$file = fopen(path($name), "w "); $GLOBALS['mresources'][$name] = $file;
flock($resource, LOCK_EX)
should be called to lock the file resource, acting like a blocking mutex.
From this step to step guide we can already draw a few problems:
How can we be really sure that
$GLOBALS['mresources']
is not defined already? In a multithreading environment, when thread A might be still checking if the value of$GLOBALS['mresources']
is an array or it's still empty, another thread might be already allocating its value on that exact time. So, the problem here is that thread A could allocate its value 2x. This way,$GLOBALS['mresources']
could lost all file resource references stored inside it by accident.How can we prevent the same thing from happening here:
$GLOBALS['mresources'][$name] = $file;
? If client B checks that$GLOBALS['mresources']['log']
doesn't exist, and client C takes the exact same action and result, how can we synchronize the access to$GLOBALS['mresources']['log']
?
The answer is: use a mutex!
But here's the problem: I am creating an API for initializating mutexes, and I'm still stucked on this problem.
The reason I want to execute a PHP script right after Apache2 starts running:
I simply want to create a global mutex to handle the access on $GLOBALS['mresources']
. This way I can synchronize multiple threads from creating duplicates of the same file resource, and ensuring that they do not overwrite its value by accident.
But I just don't know how to do that. I want to execute this script right after Apache2 initializes its PHP instance, and before any client request is made. I've only found examples of running a separate PHP script, by modifying a bash script that Apache2 uses to start the Web Server. But these implementations run on a different process.
What I wanted to do is to call a script to define a file resource inside the Apache2 PHP's context, not to run a different PHP process.
Is there a way to do that?
I get it. Global variables are bad. But besides that, is there a way to do that?
CodePudding user response:
After some research on global variables, I found out that data stored inside $GLOBALS persists between sessions.
I suggest you do some more research, and real-life testing, because that is simply not true. PHP is often described as having a "shared nothing" model: every request starts with a completely clean environment, no data or even custom function and class definitions are carried across.
In order to have any "cross-request" data, you need something outside of the PHP environment such as in shared memory on the server ... or on the file system.
Which brings us right back around to your idea of locking files - you propose to associate a PHP variable with an (open) file, then lock it:
function mutex_lock(string $name): resource {
if ( ! isset($GLOBALS['mresources'][$name]) ) {
$file = fopen(path($name), "w ");
$GLOBALS['mresources'][$name] = $file;
}
$resource = $GLOBALS['mresources'][$name];
flock($resource, LOCK_EX);
return $resource;
}
But the lock doesn't stop other processes/threads locking the PHP variable, it stops them locking the file. So you don't actually need to "remember" the file handle between processes - in fact, you don't want to, because it's the one that has the lock, so if you shared it, you're sharing the lock, and it's not "exclusive" any more.
Because the file name is just based on the lock name, there isn't actually anything you need to remember, and you can remove the global variable completely:
function mutex_lock(string $name): resource {
$resource = fopen(path($name), "w ");
flock($resource, LOCK_EX);
return $resource;
}
The returned resource essentially "is" the lock: let it go out of scope, and it will automatically be closed and the file unlocked; or pass it to fclose
to do so explicitly.
You then just need to decide what you want to happen when the lock can't be acquired, and you have your mutex.