How PHP-CGI 100% CPU Usage Relates to the file_get_contents Function

[Author: Zhang Yan Version: v1.0

Reprint must indicate the original link: http://blog.s135.com/file_get_contents/]

Sometimes, on a Linux server running Nginx and PHP-CGI (php-fpm) web services, the system load suddenly spikes. Using the top command, you can see many php-cgi processes with CPU usage close to 100%. Later, through tracing, I found that this situation is closely related to PHP’s file_get_contents() function.

In large and medium-sized websites, API calls based on the HTTP protocol are commonplace. PHP programmers prefer to use the simple and convenient file_get_contents("http://example.com/") function to fetch the returned content of a URL. However, if the website http://example.com/ is slow to respond, file_get_contents() will just hang there and will not time out.

We know that in php.ini, there is a parameter max_execution_time that can set the maximum execution time for a PHP script, but in php-cgi (php-fpm), this parameter does not take effect. What truly controls the maximum PHP script execution time is the following parameter in the php-fpm.conf configuration file:

  1. The timeout (in seconds) for serving a single request after which the worker process will be terminated  
  2. Should be used when 'max_execution_time' ini option does not stop script execution for some reason  
  3. '0s' means 'off'  
  4. <value name="request_terminate_timeout">0s</value>  

The default value is 0 seconds, which means the PHP script will keep executing indefinitely. As a result, when all php-cgi processes are stuck on the file_get_contents() function, this Nginx+PHP WebServer can no longer process new PHP requests, and Nginx will return a “502 Bad Gateway” error to users. Modifying this parameter to set a maximum PHP script execution time is necessary, but it only treats the symptom, not the root cause. For example, if you change it to <value name="request_terminate_timeout">30s</value>, and a slow webpage fetch occurs in file_get_contents(), this means 150 php-cgi processes can only handle 5 requests per second, and the WebServer is still likely to encounter the “502 Bad Gateway” error.

To achieve a complete solution, PHP programmers must change the habit of directly using file_get_contents("http://example.com/") and modify it slightly by adding a timeout, using the following method to implement HTTP GET requests. If you find it troublesome, you can encapsulate the following code into a function yourself.

  1. <?php  
  2. $ctx = stream_context_create(array(  
  3.    'http' => array(  
  4.        'timeout' => 1 //Set a timeout value, in seconds  
  5.        )  
  6.    )  
  7. );  
  8. file_get_contents("http://example.com/", 0, $ctx);  
  9. ?>  

Of course, php-cgi processes hitting 100% CPU isn’t always caused by this one issue. So, how do you confirm that the file_get_contents() function is the culprit?

First, use the top command to identify the php-cgi processes with high CPU usage.

top – 10:34:18 up 724 days, 21:01,  3 users,  load average: 17.86, 11.16, 7.69
Tasks: 561 total,  15 running, 546 sleeping,   0 stopped,   0 zombie
Cpu(s):  5.9%us,  4.2%sy,  0.0%ni, 89.4%id,  0.2%wa,  0.0%hi,  0.2%si,  0.0%st
Mem:   8100996k total,  4320108k used,  3780888k free,   772572k buffers
Swap:  8193108k total,    50776k used,  8142332k free,   412088k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                              
10747 www       18   0  360m  22m  12m R 100.6 0.3    0:02.60 php-cgi                                                                                                              
10709 www       16   0  359m  28m  17m R 96.8  0.4    0:11.34 php-cgi                                                                                                              
10745 www       18   0  360m  24m  14m R 94.8  0.3    0:39.51 php-cgi                                                                                                              
10707 www       18   0  360m  25m  14m S 77.4  0.3    0:33.48 php-cgi                                                                                                              
10782 www       20   0  360m  26m  15m R 75.5  0.3    0:10.93 php-cgi                                                                                                              
10708 www       25   0  360m  22m  12m R 69.7  0.3    0:45.16 php-cgi                                                                                                              
10683 www       25   0  362m  28m  15m R 54.2  0.4    0:32.65 php-cgi                                                                                                              
10711 www       25   0  360m  25m  15m R 52.2  0.3    0:44.25 php-cgi                                                                                                              
10688 www       25   0  359m  25m  15m R 38.7  0.3    0:10.44 php-cgi                                                                                                              
10719 www       25   0  360m  26m  16m R  7.7  0.3    0:40.59 php-cgi

  Pick the PID of one of the php-cgi processes using 100% CPU, and trace it with the following command:

strace -p 10747

  If the screen displays:

select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)
select(7, [6], [6], [], {15, 0})        = 1 (out [6], left {15, 0})
poll([{fd=6, events=POLLIN}], 1, 0)     = 0 (Timeout)

  Then, you can confirm that the issue is caused by file_get_contents().

Leave a Comment

Your email address will not be published.