<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></title> 
<link>http://jackxiang.com/index.php</link> 
<description><![CDATA[赢在IT，Playin' with IT,Focus on Killer Application,Marketing Meets Technology.]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></copyright>
<item>
<link>http://jackxiang.com/post//</link>
<title><![CDATA[[实践好文]PHP 死锁问题分析]]></title> 
<author>jack &lt;xdy108@126.com&gt;</author>
<category><![CDATA[Php/Js/Shell/Go]]></category>
<pubDate>Mon, 13 Jun 2016 07:34:08 +0000</pubDate> 
<guid>http://jackxiang.com/post//</guid> 
<description>
<![CDATA[ 
	背景：对于死锁的问题，人们往往想到出现一些关于访问很缓慢，有白页现象，要是测试环境（我就真实遇到测试环境有本文谈及一样的问题）你也就重启一下PHP的php-fpm进程发现又好了，隔一段时间又出类似的问题，你会看下日志，你会发现有很多日志是“Max execution timeout of 60 seconds exceeded”，你会发现这可能是一些php的守护进程导致的，你为了解决测试环境的问题，于是觉得应该把那个php-fpm的进程数开多点，可能会好一些，于是你开多了，一直没有面对这个问题的原因，为什么呢，因为公司装PHP的是运维装的，你没有办法或时间去装一个debug版本的php，你说这个问题让运维的人来查，你觉得能查出来？So，这个问题一拖再拖，但就是没解决，但是有一天你发现磁盘满了，用du去看整体时发现满了，但是如果一个个目录去看发现并没有占用多少，也万万没有想到PHP的死锁还会导致磁盘空间占用太多，上面这种情况我就真实遇到过，后来重新reboot操作系统，磁盘又回来了，所以，我认为是一篇好文章，所以转了此文，也想说明对于PHP的扩展这方面代码质量把关需要严格，再就是PHP本身关于锁这块要弱化（除开cookie/session和cache锁外，其它能不用就不用），尽可能少用锁，这是博主一点小看法，同时把那些比如发短信啥的能异步的给用swoole异步了（这儿有点广告嫌疑），解放出php-fpm进程，防止因为阻塞导致hold住了PHP的php-fpm进程，像php下面的队列demon这种，能单独放就单独放一台机器隔离开，减少出现错误和问题的各种猜忌影响判断，下面言归正传。<br/><br/><br/>引子：<br/>本期我们邀请到了 云盘服务端 团队的技术达人- 徐铁成，一个隐蔽已久的PHP死锁问题被层层掘出，感谢铁成为我们带来这次畅快的体验，小伙伴们，准备好这次技术之旅了么？<br/>---------------<br/>发现问题<br/>近期发现线上很多机器的磁盘空间报警， 且日志文件已经清理，但是磁盘空间没有释放。通过ps aux &#124; grep php-cgi 发现， 很多进程的启动时间在几天到几周甚至几个月之前。我们线上的php-cgi都有最大执行次数的。一般在1天内都会重启一次。初步结论，这些cgi进程有问题。<br/>通过lsof -p [pid] 发现， 启动时间很久的cgi进程中打开了一些日志文件句柄，并且没有关闭。这些日志文件在文件系统中已经删除了。但是句柄没关闭，导致磁盘空间没有释放。到此，磁盘空间异常的问题基本确定。是由于cgi没有关闭文件句柄造成的。<br/>进一步分析进程， strace -p [pid]， 发现所有异常的进程都阻塞与 fmutex 状态。换句话所，异常的cgi进程死锁了。进程死锁导致打开的文件句柄没有关闭，所以导致磁盘空间异常。<br/><br/>为什么cgi进程会死锁呢？<br/><br/>什么是死锁<br/>学过操作系统的通同学，都了解多线程的概念。在多线程中访问公共资源，需要对资源加锁。访问结束后，释放锁。如果没有释放锁，那么下一个线程来获取资源的时候就会永远都无法获取资源的锁，于是这个线程死锁了。那么CGI是多线程的公共资源访问导致的死锁吗？ 答案是NO。<br/>1. CGI 是单线程进程，通过ps 就能看到。（进程状态 Sl的才是多线程进程）。<br/>2. 即使是多线程的，死锁发生在PHP的shutdown过程中调用glibc 中time 函数的位置，不是php模块造成的。而glibc 中的time相关函数是线程安全的，不会产生死锁。<br/><br/>那是什么导致的死锁呢？<br/>通过分析linux中死锁产生的机制，发现除了多线程会产生死锁外，信号处理函数同样会产生死锁。那么cgi是由于信号处理导致的死锁吗？在这之前介绍一个感念。<br/><br/>函数的可重入性与信号安全<br/>函数可重入是指，无论第几次进入该函数，函数都能正常执行并返回结果。那么线程安全函数是可重入的吗？答案是NO。 线程安全函数，在第一次访问公共资源时，会获取全局锁。如果函数没有执行完成，锁还没释放，此时进程被中断。那么在中断处理函数中，再次访问该函数，就会产生死锁。那么什么样的函数才可以在中断处理函数中访问呢？ 除了没有使用全局锁的函数，还有一些signal safe的系统调用可以使用。调用任何其他的非signal safe的函数都会产生不可预知的后果（比如 死锁）。 详见 man signal。在分析死锁的原因前，我们先看看cgi执行的流程，分析其中有没有产生死锁的可能。<br/><br/>PHP-CGI的执行流程<br/>Glibc中的时间函数使用到了全局锁，保证函数的线程安全，但没有保证信号安全（signal safe）。经过之前的分析，我们初步怀疑死锁是由于PHP-CGI进程接收到了一个信号，然后在signal handle中执行了非signal safe的函数。主流程在中断前，正在执行glibc中的时间函数。在函数获取的锁没释放前，进入中断流程。而中断过程中又访问了glibc中的时间函数。于是导致了死锁。<br/>PHP-CGI的执行流程，如下图所示：<br/><a href="http://jackxiang.com/attachment.php?fid=447" target="_blank"><img src="http://jackxiang.com/attachment.php?fid=447" class="insertimage" alt="点击在新窗口中浏览此图片" title="点击在新窗口中浏览此图片" border="0"/></a><br/>进一步分析发现，所有死锁的cgi进程的sapi_global中都记录了一个错误信息<br/>“Max execution timeout of 60 seconds exceeded”.<br/>60s 是我们php-cgi中设置执行超时。所以我们确认了，cig在执行过程中的确产生了超时异常，然后由于longjmp进入了shutdown过程。在shutdown过程中访问了glibc中的时间函数。导致了死锁。<br/>void zend_set_timeout(long seconds)<br/>&#123;<br/>TSRMLS_FETCH();<br/><br/>EG(timeout_seconds) = seconds;<br/>if(!seconds) &#123;<br/>return;<br/>&#125;<br/>……<br/><br/>setitimer(ITIMER_PROF, &amp;t_r, NULL);<br/>signal(SIGPROF, zend_timeout); // 此处会调用zend异常处理函数<br/>sigemptyset(&amp;sigset);<br/>sigaddset(&amp;sigset, SIGPROF);<br/><br/>……<br/>&#125;<br/>通过gdb调试发现，所有PHP-CGI都阻塞在zend_request_shutdown中。zend_request_shutdown会调用用户自定义的php脚本中实现的shutdown函数。如果CGI执行超市，那么定时器会产生SIGPROF信号使执行流程中断。如果此时脚本刚好处于调用时间函数的状态，且还没有释放锁资源。然后执行流程进入了 timeout 函数，继续跳转到zend_request_shutdown。此时如果自定义的shutdown函数中访问了时间函数。就会产生死锁。我们从代码中找到了证据：<br/>register_shutdown_function (&#039;SimpleWebSvc:: shutdown’);<br/>我们在php代码中使用qalarm系统，qalarm系统会在cgi执行结束（shutdown）的时候，注入一个钩子函数，来分析cgi执行是否正常，如果不正常，则发送报警信息。而刚好qalarm的报警处理函数中访问了时间函数。于是就有一定的概率产生死锁。<br/><br/>结论<br/>通过上面的分析，我们找到了cgi死锁产生的原因，是应为在signal handler中使用了非signal safe的函数，导致了死锁。<br/><br/>解决办法<br/>去掉或简化qalarm注册到shutdown中的钩子函数。避免不安全的函数调用。<br/><br/>来自：http://www.v2gg.com/lady/shishangzixun/20140924/57266.html
]]>
</description>
</item><item>
<link>http://jackxiang.com/post//#blogcomment</link>
<title><![CDATA[[评论] [实践好文]PHP 死锁问题分析]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>http://jackxiang.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>