现在的位置: 首页 > 综合 > 正文

慎用Reactor Notify机制

2013年05月19日 ⁄ 综合 ⁄ 共 2900字 ⁄ 字号 评论关闭
 

8               慎用Reactor Notify机制

Reactor的模式,有一种辅助的通知机制,Notify机制,简单说就是通过通知发起者调用notify函数,notify的消息被保存在一个管道中,handle_event的处理中会检查这个管道中是否有通知数据,如果有就根据通知的消息,会根据默认的通知消息的类型去调用hanle_input等函数。

从设计的角度将,这个机制无疑是非常优美的,对于Reactor,它在IO驱动以外,提供了一种新的驱动方式。但是从实现角度来讲,这个机制要慎用。原因有两个。

8.1               ACE Reactor的默认Notify方式采用的是ACE_Pipe

ACE Reactor的默认Notify方式采用的是ACE_Pipe,所以ACE_PipeWindowsLinux平台上的问题,Notify机制把ACE_Pipe的缺陷一个不少的继承了,而且问题更加多。

  /**

   * Contains the ACE_HANDLE the ACE_Dev_Poll_Reactor is listening

   * on, as well as the ACE_HANDLE that threads wanting the attention

   * of the ACE_Dev_Poll_Reactor will write to.

   */

  ACE_Pipe notification_pipe_;

原来在调试ACE代码的时候,我发现只要一使用Reactor,即使只使用定时器(除非明确不使用Notify),防火墙都会报警有监听端口。我曾经对此大惑不解,直到读了ACE的这部分原代码。这样做的坏处有很多。第一个是由于采用的阻塞IO。速度会慢很多,第二个由于是单线程的处理,如果在压力极大的情况下,可能出现死锁的问题。比如在有大规模的Notify的情况下,发送缓冲区很可能会被塞满(由于是单线程,这时不会有接受者),同时由于为了简化,ACE_Pipe采用的IO是阻塞的,所以会导致整个程序死锁。第三就是这样的情况下ACE_Pipe会打开一个临时的端口,而且会绑定所有的IP0.0.0.0),如果对于一个安全要求严格的的场景,这个将是一个不可饶恕的错误。【注】

 

【注】在一个安全要求严格的环境下,这个临时端口轻则可以让你的服务器轻易陷于崩溃,重则可以让你整个网络被黑客攻陷。

 

不过还好的是ACE的开发者估计自己也意识倒了这个麻烦。所以提供了另外一种消息队列的方式。你可以通过定义ACE_HAS_REACTOR_NOTIFICATION_QUEUE的宏编译ACE,这样ACE将不使用ACE_Pipe作为Notify消息的管道,而使用一个自己的内存队列保存Notify消息,这个队列是动态扩展的。而且由于是内存操作,性能方面没有太大问题。

 

大体位置在重复编译的卫哨后面,#include /**/ "ace/pre.h"前面。保证这个宏起到作用。

#ifndef ACE_CONFIG_LINUX_H

#define ACE_CONFIG_LINUX_H

 

//使用内存队列作为Notify Queue

#define ACE_HAS_REACTOR_NOTIFICATION_QUEUE

 

#include /**/ "ace/pre.h"

 

这个问题到5.6.1还是存在的,估计由于历史的原因,在很长一段时间也不会得到解决。

8.2               考虑不周的Reactor Notify机制

同上,这也应该是一个BUGReactor Notify的代码有考虑不周的地方。Notify机制的本质是提供了一条消息队列让大家有方法调用Event_handler,但是存在一种可能,在你的通知消息在消息队列的时候,Event_hanlder由于后面的处理可能已经handle_close了。但是ACEdispatch_notify却没有考虑倒这一点(或者说考虑倒这一点也不好解决)。

ACE_Select_Reactor_Notify::dispatch_notify函数的代码。

int

ACE_Select_Reactor_Notify::dispatch_notify (ACE_Notification_Buffer &buffer)

{

…………

ACE_Event_Handler *event_handler =

        buffer.eh_;

 

      bool const requires_reference_counting =

        event_handler->reference_counting_policy ().value () ==

        ACE_Event_Handler::Reference_Counting_Policy::ENABLED;

    //如果此时这个ACE_Event_Handler已经被handle_close了,你如何是好。。。。

      switch (buffer.mask_)

        {

        case ACE_Event_Handler::READ_MASK:

        case ACE_Event_Handler::ACCEPT_MASK:

          result = event_handler->handle_input (ACE_INVALID_HANDLE);

这个bug5.6.1还没有解决。我觉得这个问题是可以解决的(暂时还没有提BUG),但是得到解决的方式却仍然是低效的方案(还记得取消定时器的那个缺陷吗)。

如果你仔细看过上面的几节,你也许会发出惊叹,啊,又是Reactor Notify?对,又是它。看起来我好像一直在和ACENotify机制在做对,但它的确让我吃了无数的苦头。这部分的设计的确有一点画蛇添足的感觉,而且由于跨平台性等原因,这个东东的实现一直不如意。其实自己使用ACE的实现(比如Message_Queue)一套这样的机制应该是易如反掌的事情。不苛求了。

如果你用不到Notify机制,最好在ACE_Reactor初始化的时候彻底关闭Notify机制。很多Reactor的初始化函数都提供了关闭notify pipe的方式。比如ACE_Select_Reactor_Topen函数的disable_notify_pipe参数。当其为1的时候表示关闭notify 管道。

//disable_notify_pipe参数为1时表示关闭NOTIFY PIPE,不使用他

template <class ACE_SELECT_REACTOR_TOKEN> int

ACE_Select_Reactor_T<ACE_SELECT_REACTOR_TOKEN>::open

  (size_t size,

   int restart,

   ACE_Sig_Handler *sh,

   ACE_Timer_Queue *tq,

   int disable_notify_pipe, /*  等于==1表示关闭notify机制 */

   ACE_Reactor_Notify *notify)

 

抱歉!评论已关闭.