12

Do you know any solution to recover from the PHP fatal error : "Allowed memory size ... exhausted"

I have a shutdown function that is called when a fatal error appear. This function create an ErrorException from it, and logs it.

The problem is : when there is no more memory available, it can't log the error (I log in Firebug, via FirePHP, with Zend Framework).

So what i mean by "how to recover from it", is how to perform basic error log, and let Zend Framework send the Headers, so that the error is logged (in Firebug in my case) as any other error ?

Thanks

Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261

7 Answers7

8
if((memory_get_usage() / 1024 /1024) < 70)

I simply divide the memory_get_usage by 1024 squared to compare it to a 'normal' megabyte value of '70'.

I ran into memory problems with php inside a for loop and wrote this simple if statement to prevent my script from setting off a fatal error. In addition, the server I was operating on didn't allow me to modify the memory limit (this is often the case in some cloud offerings like openshift or large web hosts like dreamhost.) I didn't really notice any serious performance degradations (in php 5.3 which may handles functions such as this slightly differently than php 4.x or 5.x... at any rate the performance implication of a script giving a fatal error outweighs any overhead the function call may force. And would also prevent a runaway script from consuming all available ram.

Many may argue; oh hey your software isn't optimized. Yes. You're probably right; but with complex data sets you can only squeeze so much performance out before you need to throw more memory at it; and hunting down memory errors in an ajax flow can be very frustrating; especially when you aren't sure where your log files are.

redcap3000
  • 928
  • 1
  • 8
  • 15
  • great answer! just what i was looking for... should have focused less on telling a personal story an more on the actual solution though. Curious to know what you put in the conditional. – I wrestled a bear once. Nov 16 '15 at 16:22
8

This error is a fatal error - that means you cannot recover from it. If PHP has hit it's memory limit, it won't be able to allocate any more memory to create your exception and any other memory it needs to carry on its execution.

There is another type of error - "catchable fatal error" which as the name suggests, can be caught in a try/catch, but unfortunately the memory size allocation is not one of them.

Andy Shellam
  • 15,403
  • 1
  • 27
  • 41
  • Is that possible to expand the memory limit during the execution ? (just to get enough memory to finish the rendering of the site) I suppose this is not possible to list all the objects in memory, and "kill" the one that is too big, like I could with processes ? (I know this is asking for the moon, but who knows) – Matthieu Napoli Feb 23 '10 at 14:47
  • Don't believe so. If it's one particular thing that's running into this problem, then you might just have to avoid the issue and raise the memory limit (for that script). Which isn't what you're wanting, but I don't believe you have any other choice. – Blair McMillan Feb 23 '10 at 14:55
  • No 'fraid not. Just out of curiosity, how much is your current memory limit, and what sort of website is it? I'm not sure what the recommended amount is, but I tend to set it to 1/4 of the RAM on the machine. Perhaps at key points in your script you could call memory_get_usage and if it's getting high, increase the memory_limit setting before it trips? Not sure without knowing your script's layout and purpose. – Andy Shellam Feb 23 '10 at 14:59
  • Looking at the example on the manpage (http://www.php.net/manual/en/function.memory-get-usage.php) it looks like calling that function triggers the garbage collector, so maybe unsetting some un-needed variables before calling that function may help? – Andy Shellam Feb 23 '10 at 15:00
  • The limit is actually 128M. But currently I have about 10 Exceptions (that I know of, and that will be fixed, but until then they have no impact on the website) that are logged to Firebug via an exception handler. So maybe this is what is causing the memory usage (an Exception seems to be a very big object). If that is possible to extend the memory limit, i'll do so ? (just a bit, to flush the headers). The garbage collector seems a good idea too. – Matthieu Napoli Feb 23 '10 at 15:08
  • 1
    Please refer to gordons answer – Matthew A Thomas Feb 11 '16 at 10:54
  • @MatthieuNapoli @BlairMcMillan `ini_set('memory_limit', '512M')` can be sent mid-execution to quadruple the default memory limit. – mpag May 22 '17 at 21:37
  • Gotta love the "you can't" answers. – Captain Hypertext Apr 22 '20 at 20:40
5

The regular way to customize error handling is through

set_error_handler — Sets a user-defined error handler function

The docs for this function state (emphasis mine):

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

So, it won't work regularly, but you can try

As of PHP7, Errors and Exceptions are Throwables, so you can try/catch them:

Gordon
  • 312,688
  • 75
  • 539
  • 559
  • 4
    register_shutdown_function is the solution I am currently using, and it works fine – Matthieu Napoli Feb 23 '10 at 15:27
  • @Matthieu Cool to know. Happy I could help. – Gordon Feb 23 '10 at 15:46
  • register_shutdown_function does not work fine. It only allows you to run your cleanup functions. If you decide to try to continue execution within register_shutdown_function and you hit another fatal error php will terminate straight (most likely with a 500 internal server error). – Pacerier Jul 13 '13 at 11:19
  • http://stackoverflow.com/questions/8440439/safely-catch-a-allowed-memory-size-exhausted-error-in-php – Matthew A Thomas Feb 11 '16 at 10:55
2

PHP errors are sent by default to your apache error log /path/to/apache/logs/error.log and you can see it there.

nickf
  • 537,072
  • 198
  • 649
  • 721
  • Please forgive me; I appreciate your help, but I believe I was not clear in my question. I log to various destinations, I can read the error, I can do a var_dump, but I don't want to, this is a professional website. I want to be able to log the error and flush the headers once this error appears. – Matthieu Napoli Feb 23 '10 at 14:42
  • *"The problem is : when there is no more memory available, it can't log the error"* It sounded like you wanted to log the error. – nickf Feb 24 '10 at 00:50
2

Got an idea for an untested trick and I'd be happy to know if it helped. Allocate some global variable when you first register the shutdown function and release it when the shutdown function's code is first executed. You may then have sufficient memory to create the Exception object. Let me know if it worked and please publish the code here.

user323506
  • 21
  • 1
  • this does work, just use get_peak_memory_usage before and after your exception call to figure out how much memory you need to allocate and set a variable of that size in $GLOBALS, then unset it at the top of the shutdown function – profitphp Dec 06 '11 at 22:11
  • @profitphp: the correct syntax is memory_get_peak_usage() :) see http://php.net/manual/en/function.memory-get-peak-usage.php – hardcoder Jun 17 '15 at 09:56
2

This worked fine for me:

try {
    ini_set('memory_limit', (ini_get('memory_limit')+1).'M');
} catch(Exception $e) {}

This assumes your memory limit is in the format 123M.

Tgr
  • 27,442
  • 12
  • 81
  • 118
  • When do you execute this code? Is it when you get the fatal error? – Matthieu Napoli Nov 23 '11 at 17:03
  • In the shutdown function, right after determinig that `error_get_last()` is not empty. – Tgr Nov 23 '11 at 17:38
  • why do you append the 'M' on `ini_get()` method? – Emma Feb 16 '16 at 12:14
  • 1
    `memory_limit` is a string that typically has an integer part and a character at the end which specifies the units (like `K` or `M`). This code assumes you know the unit is `M`; doing `+1` on it will cause an implicit conversion to integer (ie. the `M` at the end is dropped) so I needed to re-add it. If you want to handle all possible memory limit values, the code will be a bit more complex (see the [php.net example](http://php.net/manual/en/function.ini-get.php)). – Tgr Feb 16 '16 at 17:40
  • Are you tested this solution at a real project that use more memory while shutdown? (i think it may cant do it because of `ini_get`,`plus`,`concat` needs more memory) – a55 Jan 16 '21 at 02:37
  • 1
    @a55 only a trivial amount. If you are concerned about it, you can reserve some buffer like done e.g. [by MediaWiki](https://github.com/wikimedia/mediawiki/blob/master/includes/exception/MWExceptionHandler.php#L89). – Tgr Jan 17 '21 at 03:40
1

One I can think of is that you when you're doing your memory intensive operation you manually query memory_get_usage() on a regular basis (e.g. every loop iteration) and dump out your headers/error when it goes over some failsafe value which is below the script limit. It will slow your script down a lot, but at least you'll get something back.

Or, and you may not be able to do this, run the memory intensive stuff as a CLI-based script called from inside your web-based stuff by using exec. The CLI part might fall over, but the web part will be able to report on it.

gavtaylor
  • 653
  • 8
  • 21
Mike
  • 2,523
  • 1
  • 18
  • 9
  • Thanks, I will check out the get_memory_usage() function, but I want a generic solution for this problem, this is not about a specific page that requires a lot of memory (I use CLI scripts, as you said, for that). So I can't check get_memory_usage() on a regular basis. – Matthieu Napoli Feb 23 '10 at 15:31
  • Oops, it's `memory_get_usage()`, has been ages since I used that one. – Mike Feb 23 '10 at 15:33