Linux异步信号处理函数引发的死锁及解决方法

死锁的发生

自己所在的团队在开发新版本过程中,一次测试环境发生了server死锁,整个server的任务线程都被hang住。而死锁的代码就在我负责的程序日志部分中localtime_r函数调用处。

程序日记需要记录打印日志的时间,而localtime_r函数就是用于将系统时间转换为本地时间。同样功能的函数还有localtime。两个函数的区别是:localtime_r是thread-safe,其返回的结果存在由用户提供的buffer中;而localtime返回的结果是指向static变量,多线程环境可被其他线程修改。localtime_r实现中有一把锁,负责lock tzfile中的状态变量,而server就在这里发生死锁。

经过分析死锁是由于发kill信号,信号处理函数引起的。原线程打印程序日志获得localtime_r中需要的锁后,kill信号触发中断处理,正好分配给该线程处理中断。信号处理函数中再次打印日志,调用localtime_r的锁时发生死锁。

之前的信号处理方式为异步方式,同时信号处理函数中做了很多事情。之前大家一直关注线程安全,却从来没有注意过异步信号处理函数的安全性。所在项目之前的信号处理函数实现一直是这个方案,但这次最新版本由于还在开发中,大家调用了大量日志打印,增加了死锁的概率才将这个问题暴露出来。这也暴露了部分代码场景思考不充分,测试不足。

Signal Handling and Nonreentrant Functions

信号处理函数不推荐做太多工作,如果调用函数需要是reentrant。reentrant可重新进入的,可以理解为一次调用发生后,不会对该函数的再次调用发生任何影响。即reentrant函数中不可以有static或global变量,不可以分配释放内存,通常不可以使用修改用户提供的对象,修改errno等等。
具体可以看
http://www.gnu.org/software/libc/manual/html_node/Nonreentrancy.html#Nonreentrancy

解决信号处理带来的死锁 异步变同步

自己的第一直觉是既然信号处理函数不可以做太多工作,需要调用non-reentrant函数,那就把日志打印全部去掉好了。但发现,所在项目的信号处理函数中会做大量工作,许多调试方法和调试信息通过kill信号获得,而且这些调用基本都是non-reentrant。所以只能修改信号处理的方案。

信号处理的方式除了异步使用方式还有同步使用方式。同步信号处理方式即指定线程以同步的方式对从信号队列中获取信号进行处理。主要调用函数为sigwait,流程:
1、主线程设置信号掩码,设置希望同步处理的信号;主线程的信号掩码会被创建的线程继承;
2、创建信号处理线程,信号处理线程循环调用sigwait(sigtimedwait)等待希望同步处理的信号并做信号处理;
3、创建其他线程。

可参考http://www.ibm.com/developerworks/cn/linux/l-cn-signalsec/

异步信号处理死锁复现测试

#include <gtest gtest.h="">
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
//signal function should not call I/O function and nother non-reentrant functions
//this test would have a dead lock. Because localtime_r have a lock. When signal interrupt
//occurs, if the main function get the lock and had not released it, the dead lock produced.
void handler(int signum)
{
  char result[100];
  time_t now;
  struct tm time1;
  now = time(NULL);
  localtime_r(&now, &time1);
  strftime(result, 100, "%T", &time1);
  printf("At %s, user pressed Ctrl-C\n", result);
}

int main (void)
{
  time_t now;
  struct tm ltime;

  if (signal(41, handler) == SIG_IGN)
signal(41, SIG_IGN);

  now = time(NULL);
  while(1) {
    localtime_r(&now, <ime);
  }
  return 0;
}

同步信号处理方式

程序中tbsys使用见http://blog.csdn.net/michaelyang_yz/article/details/49056213

#include <gtest gtest.h="">
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include "tbsys.h"

namespace test {

bool g_run_test = false;
void signal_handler(int signum)
{
  printf("singal num: %d", signum);
  char result[100];
  time_t now;
  struct tm time1;
  now = time(NULL);
  localtime_r(&now, &time1);
  strftime(result, 100, "%T", &time1);
  printf("At %s, user pressed Ctrl-C\n", result);
}

class ObSignalDealThread: public tbsys::CDefaultRunnable
{
public:
  virtual void run(tbsys::CThread *thread, void *arg);
};

class ObSignalTest: public ::testing::Test
{
public:
  void run_test();
protected:
  ObSignalDealThread signal_deal_thread_;
};

void ObSignalDealThread::run(tbsys::CThread *thread, void *arg)
{
  UNUSED(thread);
  UNUSED(arg);
  sigset_t   waitset;
  intsignum;
  sigemptyset(&waitset);
  sigaddset(&waitset, SIGINT);
  struct timespec timeout = {1, 0};
  while (!_stop) {
if ( -1 == (signum = sigtimedwait(&waitset, NULL, &timeout))) {
  //do not log error, because timeout will also return -1.
  printf("time out or error, errno=%d, errmsg=%s\n", errno, strerror(errno));
} else {
  printf("sigwaitinfo() fetch the signal: %d\n", signum);
  signal_handler(signum);
}
  }
}

void ObSignalTest::run_test()
{
  if (g_run_test) {
//first sigmask in main thread
sigset_t bset, oset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);
if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
  printf("!! Set pthread mask failed\n");

//second start signal deal thread
signal_deal_thread_.start();

//loop call localtime_r
time_t now;
struct tm ltime;
now = time(NULL);
while(1) {
  localtime_r(&now, <ime);
}
signal_deal_thread_.wait();
  }
}

TEST_F(ObSignalTest, signal_test)
{
  run_test();
}
}

//use ./test_signal_handle run
int main(int argc, char **argv)
{
  ::testing::InitGoogleTest(&argc,argv);
  if (argc >= 2) {
    if (strcmp("run", argv[1]) ==0) {
      ::test::g_run_test = true;
    }
  }
  return RUN_ALL_TESTS();
}
时间: 2024-12-28 13:00:31

Linux异步信号处理函数引发的死锁及解决方法的相关文章

Linux系统出错提示[root@localhost ~]# iptraf的解决方法

我们都知道Linux系统的很多操作都需要命令来执行,在执行命令的过程中,有时会遇到[root@localhost ~]# iptraf错误提示,遇到这个问题要如何解决呢?下面小编就给大家介绍下Linux执行命令提示[root@localhost ~]# iptraf的解决方法. 提示: [root@localhost ~]# iptraf -bash: iptraf: command not found 要解决这个问题就需要安装相应的软件包,很多时候命令名不一定就是软件包的名字, 在这里的ipt

linux找不到动态链接库 .so文件的解决方法(转自:http://www.cnblogs.com/xudong-bupt/p/3698294.html)

linux找不到动态链接库 .so文件的解决方法 如果使用自己手动生成的动态链接库.so文件,但是这个.so文件,没有加入库文件搜索路劲中,程序运行时可能会出现找不到动态链接库的情形. 可以通过ldd命名来查看可执行文件依赖的动态链接库,如下(其中D为可执行程序):  其中的libjson_linux-gcc-4.6_libmt.so cannot found. 解决这个问题:  (1)在系统中查找这个文件(当然要保证系统中已经有这个.so文件,只是查找路径没有设置正确而已): sudo fin

jquery trigger函数执行两次的解决方法_jquery

本文实例讲述了jquery trigger函数执行两次的解决方法.分享给大家供大家参考,具体如下: 一.问题如下: 有如下代码: <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <style type="text/css"> *{margin:0;pa

jQuery Ajax Post 回调函数不执行问题的解决方法_jquery

今天在写一个检查用户名的功能时,使用的是jQuery.post( url, [data], [callback], [type] )这个函数,但是发现其中的回调函数不能执行. 先来看看我的代码: 前台代码: <script type="text/javascript"> function checkUser() { var user = $('#<%=txtUser.ClientID %>').val(); $.post('checkUser.ashx', {

Linux中提示No such file or directory解决方法

  问题描述 解决方法 分析原因,可能因为我平台迁移碰到权限问题我们来进行权限转换 1)在Windows下转换: 利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换,再放到Linux中执行.转换方式如下(UltraEdit):File-->Conversions-->DOS->UNIX即可. 2)方法 用vim打开该sh文件,输入: [plain] :set ff 回车,显示fileformat=dos,重新设置下文件格式: [plain] :set ff=uni

jquery与js函数冲突的两种解决方法

如果您还有别的要求,想继续使用原先的$(),同时还需要与别的类库不冲突的话,还有两种解决方法 其一: jQuery.noConflict(); jQuery(function($) { $("p").click(function() //在函数内继续可以使用jquery类库的$()方法 { alert($(this).text()); }) }) var JsCOM_cr = $("cr"); // 在函数外面,照样可以使用JsCOM.js的$()方法 其二: jQ

LINQ那些事儿(9)-解析Table&amp;lt;T&amp;gt;.Attach引发的异常和解决方法

起因主要是因为看到博客园又有朋友开始讨论LINQ2SQL的问题,这次说的是Attach.通过解读Attach,可以发现LINQ2SQL内部是如何维护和跟踪对象实例.如何实现延迟加载,并且还可以引发关于延迟加载和N-Tier Application中LINQ2SQL的应用技巧的讨论.本文所讨论内容适用于.Net Framework 3.5版本的LINQ2SQL,所使用数据库是Northwnd. 对于对象添加和删除操作,LINQ2SQL在Table<T>类定义中直接提供了InsertOnSubmi

linux下apt-get出现“no public key available…”解决方法

众所周知,Linux虽然比Windows的安全系数要高,但是经常更新还是个好习惯(至少近期就爆出了不少关于SSL的安全漏洞值得大家重视!),但是在Ubuntu和Debian下运行apt-get update刷新更新源的时候却经常遇到"There is no public key available for the following key IDs"的问题,具体表现为以下错误提示: W:There is no public key available for the following

linux bash中too many arguments问题的解决方法_linux shell

判断一个文件的内容是不是为空,使用语句: if test -z `cat filename` 当filename为空或者只有一行没有空格的字符串的时候,一切正常,反之,则会报:too many arguments,甚至是: binary operator expected之类的错误. 参考文章:http://www.ibm.com/developerworks/cn/linux/shell/bash/bash-2/index.html 原因分析:filename中的空格回车等迷惑了bash.如果