关于ACE_Asynch_Acceptor::accept 内存泄露的问题(Windows)

关于ACE_Asynch_Acceptor::accept 内存泄露的问题(Windows)


一、问题描述

使用ACE_Proactor和ACE_Asynch_Acceptor,开发服务器端侦听客户机的连接功能时,当程序正常逻辑退出时,会发生内存泄露的问题,有时会怀疑ACE的BUG,其实不全是如此。

一般情况下,运行ACE_Proactor的线程来自ACE_Task::activate()激活的那些线程。以下是退出时的关键步骤:

	1、调用ACE_Asynch_Acceptor::cancel()

取消当前的侦听工作,这样,就会触发ACE_Proactor框架中的handle_accept事件,携带accept失败的信息给应用程序代码。

	2、调用ACE_OS::closesocket(handle()) 关闭socket句柄,再调用handle(ACE_INVALID_HANDLE)重置变量。

这里为什么要调用关闭socket的逻辑呢。如果这里不关闭,会有一个潜在的问题,就是当有客户端连接后,在正常退出服务器程序,还是会发生同样的内存泄漏问题。关键原因在ACE_Asynch_Acceptor::cancel()里面,使用Windows的CancelIo方法,这个方法要求发起的异步操作和调用CancelIo是同一个线程,如果不是,尽管CancelIo返回true,但是其实没有取消掉listen的socket句柄。然后糟糕的是,完成端口的事件没有发生,handle_accept没有被调用,内存依然没有顺利释放。

因为,一个客户端连接后,处理这个逻辑往往是一个工作线程,比如A线程,当A完成工作后,会再次发起一个异步accept操作。恰恰是这个异步操作是由A线程发起的,而调用CancelIo却在主线程,所以出现上面的情况。

一般情况下,closesocket会在ACE_Asynch_Acceptor的析构函数中调用,但是我们等不到那时,因为我们还是希望由handle_accept()方法被调用,来处理取消或者关闭的错误逻辑。所以这里不合适删除ACE_Asynch_Acceptor对象。

	3、调用ACE_Proactor::proactor_end_event_loop()

让线程忙完当前工作后,回到ACE_Proactor::handle_events的时候,可以顺利退出。如果当前线程阻塞在完成事件的等待中,那么就会发送唤醒线程的指令,让线程们继续。这样所有线程都又回到了handle_events()的地方,也就正常顺利的结束了。

	4、调用ACE_Task::wait()

程序启动时,一般用ACE_Task::activate() 方法来启动一些线程,这些线程配合ACE_Proactor框架来处理事件并分发它们。所以,这里需要调用wait()方法等待这些线程全部正常结束。有了步骤2的工作。这里稍微等一会就返回了,返回时,可以确定一件事情,就是由activate派发出来的线程已经全部顺利、正常的退出了(从svc返回)。

	5、调用ACE_Proactor::close_singleton()

删除ACE_Proactor的单体对象。一般用单体模式,如果是其他,那么删除即可。


基本上,这个逻辑没有大问题,但是如果用内存检测工具检测后发现,有内存泄露,来自这里:

	ACE_Asynch_Acceptor<HANDLER>::accept()方法中的

	// Create a new message block big enough for the addresses and data

	  ACE_NEW_RETURN (message_block,

	                  ACE_Message_Block (space_needed),

	                  -1);

	


二、分析原因

	1、泄露的内存本来是用于AcceptEx异步调用所需的内存,但是程序结束时,上面分配的内存没有全部删除干净。也许可以发现,一共泄露了4块这样的内存,都是这个地方。为什么是4块呢?因为ACE_Asynch_Acceptor<HANDLER>::accept()一共分配了5块,这是默认情况,释放了一块,还有4块没有来得及释放,线程都退出了。
	2、ACE_Asynch_Acceptor<HANDLER>::open()在调用时,会发起5份异步的accept操作。每一份操作都走上面的accept逻辑。open方法有2个参数,就是backlog和number_of_initial_accepts,如果number_of_initial_accepts不指定(默认是-1),那么number_of_initial_accepts最后使用backlog的值,而backlog的默认值是5,所以一共是调用了5次accept()。
	3、这样默认情况下,程序启动后,就有了5个异步accept操作。假如此时让程序正常退出。我们需要的理想情况是调用5次ACE_Asynch_Acceptor::handle_accept()回调。但是往往只有一次。那是因为假如ACE_Task::activate()采用默认调用,那么只有一个线程。handle_accept里面,的确调用了这块内存释放逻辑。问题在于只调用了一次handle_accept()。


三、简单解决方案

原因找到了。接下来就想办法来解决。有两个简单的办法。

	1、在ACE_Asynch_Acceptor::open中,最后一个参数使用1,而不是默认值。这样,就调用一次accept方法,也就是一块内存。
	2、保留5次或者多次accept,假设次数为N。那么这个方案就是:在ACE_Task::activate()中,线程数量>=N,为什么呢?因为当N个accept异步操作发起后,至少有N个线程等待处理他们,如果程序退出时,至少一个线程处理一个操作。正好全部操作的handle_accept得以调用,每一块内存都得到了释放。


四、潜在问题

上面两个解决方案,其实并不是非常强壮和最棒的。比如下面这种情况下,退出程序时,还是可能发生内存泄露。

	在退出瞬间,N个线程中有3个线程在忙一些具体的事情。那么,极有可能出现3份内存泄漏。因为其中只有N-3个accept异步操作得到了handle_accept的调用,有3个可能没有机会,这是因为等这3个线程忙完工作回到handle_events时,发现proactor_end_event_loop已经调用了,所以,线程就退出了,不再处理其他事情。


	另外,如果需要单线程来执行ACE_Proactor框架,同时希望开始时的accept调用还是保留5次的话,这个办法也是行不通的。


五、其他

关于最佳的解决方案,以后再研究分析。


欢迎自由转载!:)

2013年12月19日

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值