Can I close a file by unsetting the handle? - php

I'm a little puzzled if I can spare the fclose command by just unsetting the variable that carries the handle?
$handle = fopen($file);
...
fclose($handle);
... // script goes on for a long
Compared with:
$handle = fopen($file);
...
unset($handle);
... // script goes on for a long
Insights anyone?

Thanks to the reference-counting system introduced with PHP 4's Zend Engine, a resource with no more references to it is detected automatically, and it is freed by the garbage collector.
Consider the implications of this. It's safe to assume that all traces of the variable are gone after the garbage collection. In other words, at the end of PHP's execution, if PHP is not still tracking the reference, how would it close it? Thus, it seems fairly logical that it would close it when the garbage collector eats it.
This is a bad logical argument though because it assumes that garbage collections happens either immediately or shortly after unset and that PHP does not keep hidden references to variables that no longer exist in user land.
A more compelling case though could be a potential behavioural flaw if PHP did not close file handles when they go out of scope. Consider a daemon of some kind that opens lots of files. Now consider if fclose is never called. Instead, variables are allowed to fall out of scope or unset is explicitly called on them.
If these file handles were not closed, this long running daemon would run out of file handles.
Potentially behavior specific test script:
<?php
$db = mysql_connect(...);
if ($db) {
echo "Connected\n";
sleep(5); //netstat during this just for paranoia
unset($db);
echo "Unset\n";
sleep(5); //netstat during this and the connection is closed
}
On both Windows 7 and Debian 6, the connection has been closed after the unset.
Obviously though, this only proves that on my specific machines with my specific PHP version will this work. Has no meaning on file handles or the like :).
Am searching the PHP source now for hard proof

PHP docs hint that all resources with no remaining references are "freed", I assume for file handles this would include closing the file.
Simple test case:
$f = fopen("test.php", "r");
if (!flock($f, LOCK_EX)) {
print("still locked\n");
exit;
}
unset($f);
sleep(5);
print("goodbye\n");
(I've saved this as test.php, so it is locking itself; might need to change the filename in the fopen() to some existing file otherwise)
Run the script twice within 5 seconds; if you get "still locked", then apparently unsetting the handle did not release the lock. In my test, I did not get "still locked", so apparently unsetting the handle at least releases the lock, though it would seem silly to release locks upon garbage collection, but not close the file.

unset($handle) will destroy the $handle variable, but it won't close the file being pointed by $handle. You still need to call fclose() to close the file.

some research:
fclose makes $handle to be resource(5) of type (Unknown)
while
unset makes it NULL.
and after fclose php consumes 88 bytes of memory more.
so: they are different =)

Related

PHP flock() not locking

I am having trouble figuring out why flock() is not behaving properly in the following scenario.
The following code is placed into two different PHP scripts one "test1.php" and the other "test2.php". The point of the code is to create a file which no other process (which properly uses the flock() code) should be able to write to. There will be many different PHP scripts which try to obtain an exclusive lock on this file, but only one should have access at any given time and all the rest should fail gracefully when they fail to get the lock.
The way I am testing this is very simple. Both "test1.php" and "test2.php" are placed in a web accessible directory on my server. Then from a browser such as Firefox, the first script will be executed, and then immediately after, the second script is executed from a different browser tab. This seams to work when the code is run from two different PHP scripts such as "test1.php" and "test2.php", but when the code is run twice from the same "test1.php" script or "test2.php" script the second script that is run will not immediately return with a failure.
The only reason I can think of for this, is that flock() treats all PHP processes with the same file name as the same process. If this is the case, then when "test1.php" or "test2.php" are run twice (from two different browser tabs) PHP sees them as the same process and thus does not fail the lock. But to me, it does not makes sense for PHP to be designed like that, thus I am hear to see if anyone else can solve this problem for me.
Thanks in advance!
<?
$file = 'command.bat';
echo "Starting script...";
flush();
$handle = fopen($file, 'w+');
echo "Lets try locking...";
flush();
if(is_resource($handle)){
echo "good resource...";
flush();
if(flock($handle, LOCK_EX | LOCK_NB) === TRUE){
echo "Got lock!";
flush();
sleep(100);
flock($fp, LOCK_UN);
}else{
echo "Failed to get lock!";
flush();
}
}else{
echo "bad resource...";
flush();
}
exit;
Any help with the above is greatly appreciated!
Thank you,
Daniel
I had the same situation and found the problem to be with the browser.
When making multiple requests to the same URL, even if doing so across tabs or windows, the browser is "smart" enough to wait until the first request completes, and then the browser attempts to run the subsequent request(s).
So, while it may look like the lock is not working, what is actually happening is that the browser (both Chrome and Firefox) is waiting for the first request to complete before running the second request.
You can verify that this is the case by opening the same URL once in Chrome and once in Firefox. By doing so, as I did, you would probably see that the lock is indeed working as expected.
flock has many restrictions, including multi-threaded servers, NFS volumes, etc.
The accepted solution is apparently to attempt to create a link instead.
Lots of discussion on this topic: http://www.php.net/manual/en/function.flock.php

flock call within function always 'succeeds', ignoring previous lock

To prevent multiple instances of a PHP-based daemon I wrote from ever running simultaneously, I wrote a simple function to acquire a lock with flock when the process starts, and called it at the start of my daemon. A simplified version of the code looks like this:
#!/usr/bin/php
<?php
function acquire_lock () {
$file_handle = fopen('mylock.lock', 'w');
$got_lock_successfully = flock($file_handle, LOCK_EX);
if (!$got_lock_successfully) {
throw new Exception("Unexpected failure to acquire lock.");
}
}
acquire_lock(); // Block until all other instances of the script are done...
// ... then do some stuff, for example:
for ($i=1; $i<=10; $i++) {
echo "Sleeping... $i\n";
sleep(1);
}
?>
When I execute the script above multiple times in parallel, the behaviour I expect to see - since the lock is never explicitly released throughout the duration of the script - is that the second instance of the script will wait until the first has completed before it proceeds past the acquire_lock() call. In other words, if I run this particular script in two parallel terminals, I expect to see one terminal count to 10 while the other waits, and then see the other count to 10.
This is not what happens. Instead, I see both scripts happily executing in parallel - the second script does not block and wait for the lock to be available.
As you can see, I'm checking the return value from flock and it is true, indicating that the (exclusive) lock has been acquired successfully. Yet this evidently isn't preventing another process from acquiring another 'exclusive' lock on the same file.
Why, and how can I fix this?
Simply store the file pointer resource returned from fopen in a global variable. In the example given in the question, $file_handle is automatically destroyed upon going out of scope when acquire_lock() returns, and this releases the lock taken out by flock.
For example, here is a modified version of the script from the question which exhibits the desired behaviour (note that the only change is storing the file handle returned by fopen in a global):
#!/usr/bin/php
<?php
function acquire_lock () {
global $lock_handle;
$lock_handle = fopen('mylock.lock', 'w');
$got_lock_successfully = flock($lock_handle, LOCK_EX);
if (!$got_lock_successfully) {
throw new Exception("Unexpected failure to acquire lock.");
}
}
acquire_lock(); // Block until all other instances of the script are done...
// ... then do some stuff, for example:
for ($i=1; $i<=10; $i++) {
echo "Sleeping... $i\n";
sleep(1);
}
?>
Note that this seems to be a bug in PHP. The changelog from the flock documentation states that in version 5.3.2:
The automatic unlocking when the file's resource handle is closed was removed. Unlocking now always has to be done manually.
but at least for PHP 5.5, this is false; flock locks are released both by explicit calls to fclose and by the resource handle going out of scope.
I reported this as a bug in November 2014 and may update this question and answer pair if it is ever resolved. In case I get eaten by piranhas before then, you can check the bug report yourself to see if this behaviour has been fixed: https://bugs.php.net/bug.php?id=68509

PHP Memory management and arrays

Do I need to worry about memory leaks with PHP? In particular, I have the following code that is being called from a browser. When the call finishes, is everything cleaned up properly, or, do I need to clear the memory created by the first array that was created?
class SomeClass
{
var $someArray = array();
function someMethod()
{
$this->someArray[1] = "Some Value 1";
$this->someArray[2] = "Some Value 2";
$this->someArray[3] = "Some Value 3";
$this->someArray = array();
$this->someArray[1] = "Some other Value";
$this->someArray[2] = "Some other Value";
$this->someArray[3] = "Some other Value";
}
}
someMethod();
Thanks,
Scott
Do I need to worry about memory leaks with PHP?
It's possible to have a cyclic reference in PHP where the refcount of the zval never drops to 0. This will cause a memory leak (GC won't clean up objects that have a reference to them). This has been fixed in >= PHP 5.3.
In particular, I have the following code that is being called from a browser. When the call finishes, is everything cleaned up properly, or, do I need to clear the memory created by the first array that was created?
PHP scripts have a request lifecycle (run application, return response, close application), so it shouldn't be a worry. All memory used by your application should be marked as free'd when your application finishes, ready to be overwritten on the next request.
If you're super paranoid, you can always unset things, however, PHP is a garbage collected language meaning that unless there is a bug in the core or in an extension, there is never going to be a memory leak.
More information
On a side note, you should use the newer PHP 5 OOP syntax. And, someMethod would be an error. It would need to be $obj->someMethod() where $obj is an instance of the class.
There actually do exist memory problems if you run mod_php through Apache with the mpm_prefork behavior. The problem is that memory consumed by PHP is not released back to the operating system. The same Apache process can reuse the memory for subsequent requests, but it can't be used by other programs (not even other Apache processes).
One solution is to restart the processes from time to time, for example by setting the MaxRequestsPerChild setting to something rather low (100 or so, maybe lower for lightly loaded servers). The best solution is to not use mod_php at all but instead run PHP through FastCGI.
This is a sysadmin issue though, not a programmer issue.

register_shutdown_function Or ignore_user_abort?

Hey i've seen people recommend each of them, One calimed register_shutdown_function to be better but without explination.
I'm talking about which is better to send a response back and still preform other tasks.
I Wondered what really is the better method and why.
EDIT:
In the register_shutdown_function documentation, someone published the following method:
<?php
function endOutput($endMessage){
ignore_user_abort(true);
set_time_limit(0);
header("Connection: close");
header("Content-Length: ".strlen($endMessage));
echo $endMessage;
echo str_repeat("\r\n", 10); // just to be sure
flush();
}
// Must be called before any output
endOutput("thank you for visiting, have a nice day');
sleep(100);
mail("you#yourmail.com", "ping", "im here");
?>
Could it be better then any of the functions i stated?
ignore_user_abort() tells PHP/Apache to not terminate execution when the user disconnects. register_shutdown_function simply allows you to do some cleanup while PHP is in the process of shutting down.
register_shut_down is only useful if you need to do some cleanup that PHP's normal shutdown routines wouldn't take care, e.g. removing a manually created lock file, flipping a bit in a DB record somewhere, etc...
In older versions of PHP (<4.1.0 under Apache), register_shutdown_function() would ensure that the connection was closed before your shutdown functions ran. This is no longer the case. The endOutput() function in your edit should indeed do what you want, provided you don't have any output buffers open. Though, it does set the script to be able to run forever if necessary, which could be annoying if it goes into an infinite loop (especially during debugging). You might want to change set_time_limit() to use a value that actually reflects how many seconds the script should take.
It's probably best to avoid register_shutdown_function() if you don't need it, since it has some other odd behavior (such as not being able to add a second layer of shutdown functions to run if the first shutdown function calls exit()).

How do I restore this script after a hardware failure?

I know this is a bit generic, but I'm sure you'll understand my explanation. Here is the situation:
The following code is executed every 10 minutes. Variable "var_x" is always read/written to an external text file when its refereed to.
if ( var_x != 1 )
{
var_x = 1;
//
// here is where the main body of the script is.
// it can take hours to completely execute.
//
var_x = 0;
}
else
{
// exit script as it's already running.
}
The problem is: if I simulate a hardware failure (do a hard reset when the script is executing) then the main script logic will never execute again because "var_x" will always be "1". (I already have logic to work out the restore point).
Thanks.
You should lock and unlock files with flock:
$fp = fopen($your_file);
if (flock($fp, LOCK_EX)) { )
{
//
// here is where the main body of the script is.
// it can take hours to completely execute.
//
flock($fp, LOCK_UN);
}
else
{
// exit script as it's already running.
}
Edit:
As flock seems not to work correctly on Windows machines, you have to resort to other solutions. From the top of my head an idea for a possible solution:
Instead of writing 1 to var_x, write the process ID retrieved via getmypid. When a new instance of the script reads the file, it should then lookup for a running process with this ID, and if the process is a PHP script. Of course, this can still go wrong, as there is the possibility of another PHP script obtaining the same PID after a hardware failure, so the solution is far from optimal.
Don't you think this would be better solved using file locks? (When the reset occurs file locks are reset as well)
http://php.net/flock
It sounds like you're doing some kind of manual semaphore for process management.
Rather than writing to a file, perhaps you should use an environment variable instead. That way, in the event of failure, your script will not have a closed semaphore when you restore.

Categories