分类目录归档:C/C++

其实我不懂 C++

再次修订 xunsearch-1.4.5-dev 解决内存 BUG

再次深刻体会用 C 的可怕之处了,不过纠错过程也是快乐有趣的。这次用一个 xunsearch 用户访问压力较大,数据也较多,升级到最新的 1.4.4 后仍然还会出现内存 BUG,无奈之下他采用定时重启的办法先避开。

感谢用户信任把 ssh/root 发给我,这个周末我仔细跟踪追查,辅助 gdb, core 文件等手段,再次找到3-4处存在的内存隐患,并重新修改了线程取消的处理行为,对待用户更为友好。

至此,问题全部解决,再观望一下。即将发布 1.4.5,这阵子给 xunsearch 用户们带来频繁升级不便之处敬请谅解啊,但确实能安心解决问题。

PS:Linux 上的多线程被取消时,如果不是常规的取消点而是阻塞的系统调用,居然是直接 abort() ……

1.4.5-dev 开发版下载地址:http://www.xunsearch.com/download/xunsearch-full-dev.tar.bz2

激动,终于找到 xunsearch-1.4.x 以来的潜伏 BUG

BUG 是这样的,不断有用户反馈说 xunsearch 会出现 signal 11 等各种意外的挂掉。虽然内部的保护和重生机制能保持进程自动重启,但还是非常影响用户体验,甚至偶尔还会出现内存泄露。

再多次 debug 无效后,导出 core 文件分析发现 11 信号来自 malloc 调用,这说明前面的堆内存用法错误或越界破坏了 malloc 内部维护的数据才会导致。

于是根据少得可怜的 backtrace 信息搜寻,很快定位到了问题函数 get_queryparser ,代码是这样的,大家别看后面先自己找找问题在哪?

/**
 * Get a queryparser object from cached chain
 */
static Xapian::QueryParser *get_queryparser()
{
	static struct cache_qp *head;

	pthread_mutex_lock(&qp_mutex);
	for (head = qp_base; head != NULL; head = head->next)
	{
		if (head->in_use == false)
		{
			log_debug("reuse qp (ADDR:%p)", head);
			break;
		}
	}
	if (head == NULL) /* alloc new one */
	{
		debug_malloc(head, sizeof(struct cache_qp), struct cache_qp);
		if (head == NULL)
		{
			pthread_mutex_unlock(&qp_mutex);
			throw new Xapian::InternalError("not enough memory to create cache_qp");
		}
		log_debug("create qp (ADDR:%p)", head);
		head->qp = new Xapian::QueryParser();
		log_debug("new (Xapian::QueryParser *) %p", head->qp);
		head->next = qp_base;
		qp_base = head;
	}
	head->in_use = true;
	pthread_mutex_unlock(&qp_mutex);

	head->qp->clear();
	return head->qp;
}

各位看到这可能还是并没感觉出问题,最大的问题就是 “static”,完全不应该出现在这,由于前面复制代码的时候把全局定义的宣告直接 COPY 过来于是保留了这个  static 关键字。

这还不打紧,后面不是有互斥锁吗?可是多线程调用和调度完全是不可确认的,在锁之外做了 return,在高并发压力下 return 的 head->qp 或许已经不是当年的那个 head 了。。。

于是就出现了内存混乱!!!这问题牵挂了我整整三天,这三天连发三个 xunsearch 版本,实在抱歉啊!!!

malloc 调用时产生 SIGSEGV

xunsearch searchd 在大压力下仍然很容易出现 sigsegv(11信号,内存非法访问),导出 core 文件进行分析调试发现,这个信号经常发生在 malloc() 系统调用上。

查了一些相关资料发现以下注解:

malloc can segfault for example when the heap is corrupted. Check that you are not writing anything beyond the bounds of any previous allocation.

大概哪儿非法溢出写入内存了,因此会导致这个结局。这下依赖库有点多,调查有点难,先看看!

自定义信号处理,同时又想生成 core dumped 文件

在 UNIX 环境编程时,通常会在收到异常信号时定义自己的处理函数做一些善后工作。

常见的如 SIGABRT,SIGSEGV 信号的默认行为是生成 core 文件然后终止进程,但是当您自定义处理函数后将不再生成 core 文件,但这又非常不利于调试寻找问题。

这里有个婉转办法可以两者兼得,就是在自定义信号处理函数中,恢复默认的处理行为,然后再给自己发送一个信号。示范代码

#include <stdio.h>
#include <signal.h>

static void sig_abrt(int sig)
{
   // put your codes HERE ... 
   printf("run sig_abrt()\n");
   // 还原信号默认处理行为并给自己发送同一个信号
   signal(sig, SIG_DFL);
   raise(sig);
}

int main()
{
  signal(SIGABRT, sig_abrt);
  printf("My PID: %d\n", getpid());
  raise(SIGABRT);
  sleep(100);
}

UNIX 环境多线程编程的几点注意

这个标题写得有点大,其实只是这几天在追踪修复 xunsearch 搜索进程(多线程)死锁时碰到的一系列可能问题的总结和记录。

1. 线程取消时一定要注意“取消点”,特别容易导致死锁
2. 多线程处理信号时谨防在信号处理时,其它线程又产生了别的信号从而引发不可预测的后果,该屏蔽就暂时屏蔽掉,或者加入相关的变量来判断。
3. 多线程交叉判断的全局变量,最好加上 volatile 关键字
4. 结束进程时如无特殊要求建议直接用 _exit() 而不是 exit()
5. … 慢慢补充 …