perl DBD处理超时问题

名字:

dbd-oracle-timeout.pod  测试DBD-Oracle超时操作使用Sys::SigAction


摘要:

本文讨论我使用SIGALRM来超时某个DBD-Oracle操作遇到的问题

Perl 5.8.0 和以后的版本支持sigaction() 实现'safe'的信号处理。

不幸的是,工作的斑斑在5.8之前,提出了解决这个一问题的几种方法

描述

如果你是实现一个实时业务,你的软件必须快速响应和很多的表现。

它是必要的没有任何操作话费很长的时间来完成,

资源是快速回收的,这样服务才能响应新的请求。

在这种情况下,通常最好是超时或者返回一个错误,而不是允许请求被长时间挂起

我的团队已经实现了大量的实时服务使用perl和DBI接口使用DBD-Oracle driver. 

本文针对的是使用Oracle遇到的问题,

但是 我相信 我们遇到的问题从5.6到5.8是通用的,

可能影响任何数据库驱动使用一个client库来调用像connect()

这里介绍的技术可以用于解决这类问题使用任何DBD 驱动,或者任何系统调用可能会hang的

为此 SIGALRM 已经用于中断调用

使用5.8.0之前的DBI接口,它是容易使用设置代码引用到 $SIG{'ALRM'}, 

然后使用 alarm() 来实现超时

信息处理然后die()或者其他中断调用 

我发现这两种操作需要这个处理:

1 Database Host is Down -- connect() hangs  数据库主机Down connect() hangs

使用SQL*NET ,DBI->connect()请求会hang大概4分钟,下面是我们如何处理这种情况在5.8以前的版本

eval {
   local $SIG{ALRM} = sub { die "open timed out"; };
   eval {
      alarm(2); #implement 2 second time out
      $dbh = DBI->connect("dbi:Oracle:$dbn" ... );
      alarm(0);
   };
   alarm(0);
   die $@ if $@;
};
if ( $@ ) { print "connection to $dbn timed out
" ; }

因为$SIG{ALRM} 已经被本地化,这个代码还原$SIG{ALRM}原始的值 当eval block 退出后

eval 说明


读者可能注意到 "double evals" 在上面的代码例子。

CPAN bug #50628已针对Sys::SigAction提交了bug,

问题 

我们中很多人使用perl 5.6.x 好多年了,

上面的代码工作的非常好。

我们知道 perl 5.6和以前的版本信号处理是不安全的,我们接受了风险

信号处理程序可以在一个合适的时候被调用

从5.8.2 perlvar 手册:

默认的信号传递策略从即时的(也被称为不安全)改为了延迟,也被称为安全信号

不幸的是, 'safe signals'处理导致了一些系统调用被重试之前(取决于它们是如何被调用的)

具体信号处理的执行依赖于库是如何实现的

结果是这个发生是一些调用者永远不返回,尽管信号已经被处罚。

这个例子是使用DBD-Oracle connect()请求(case 1上面的例子)

因此,标准的超时实现不在工作在perl 5.8和以后版本

eval {
   local $SIG{ALRM} = sub { die "open timed out"; };
   eval {
      alarm(2); #implement 2 second time out
      $dbh = DBI->connect("dbi:Oracle:$dbn" ... );
      alarm(0);
   };
   alarm(0);
   die $@ if $@;
};
if ( $@ ) { print "connection to $dbn timed out
" ; }


[oracle@node01 perl]$ cat test5.pl 
use DBI;
use Encode;
use Data::Dumper;
use Sys::SigAction ;
use Sys::SigAction qw( set_sig_handler );
use Sys::SigAction qw( set_sig_handler );
eval {
   local $SIG{ALRM} = sub { die "open timed out"; };
   eval {
      alarm(2); #implement 2 second time out
      $dbh = DBI->connect("dbi:Oracle://1.168.137.2:1521/serv", 'system', 'oracle') or die "can't connect to database ";;
      alarm(0);
   };
   alarm(0);
   die $@ if $@;
};
if ( $@ ) { print "connection to $dbn timed out
" ; }
[oracle@node01 perl]$ perl test5.pl 

不能超时退出

解决办法:

解决这个问题的办法(记录在perlvar手册页中)是安装信号处理使用 POSIX::sigaction().

这个提供了低级访问POSIX sigaction() system API(假设)你的系统有 sigaction().

如果你的系统没有sigaction(),  你可能没有这个问题

在那种情况下 perl 实现原始的(不安全的)信号处理方法 使用POSIX::sigaction(), 

我们可以控制信号屏蔽和sa_flags是用于安装handler,

在perl 5.8.2和以后版本,一个安全的切换是提供来使用寻求安全信号处理,


使用POSIX::sigaction()  不确保信号处理是被调用当信号是被处罚时。

调用die()程序在信号处理内 会导致系统调用被中断,控制会返回给perl 脚本。

但是这样做高效实现了返回我们不安全的信号行为  至少在 5.8.0.

在perl 5.8.2 它是要求延迟 安全的信号处理 当仍旧通知sa_flags 用于安装信号控制

perl 5.8.2 是比5.6安全

痛点

除了不能低于5.8版本 ,它需要大约4到5行代码 以前只需要设置一个localized $SIG{ALRM}.


POSIX::sigaction() 代码看起来像这样对于(connect()例子)

use POSIX ':signal_h';
 
eval {
   my $mask = POSIX::SigSet->new( SIGALRM ); #list of signals to mask in the handler
   my $action = POSIX::SigAction->new( 
       sub { die "connect failed" ; } #the handler code ref
      ,$mask ); #assumes we're not using an specific flags or 'safe' switch
   my $oldaction = POSIX::SigAction->new();
   sigaction( 'ALRM' ,$action ,$oldaction );
   eval {
      alarm(2); #implement 2 second time out
      $dbh = DBI->connect("dbi:Oracle:$dbn" ... );
      alarm(0);
   };
   alarm(0);
   sigaction( 'ALRM' ,$oldaction ); #restore original signal handler
   die $@ if $@;
};
if ( $@ ) ....


这个不是在perl 5.6一行代码的完美替换,更糟糕的是因为POSIX::sigaction() 不能工作在5.8版本以下,

我们必须让他满足perl 版本的条件

止痛药; Sys::SigAction

幸运的是,我已经被这个问题咬了一口,不想复制所有的代码在我的超时逻辑里,

我实现了一个模块 使用POSIX::sigaction() 来容易的设置一个 localized $SIG{ALRM} 

Sys::SigAction 模块可以从CPAN检索

 Sys::SigAction  模块包含了所有的POSIX:: code 到一个单独的函数请求 返回一个对象引用
 
 当对象超出范围时,它的构造器重置信号程序  因此上面的代码重写如下:
 
 use Sys::SigAction qw( set_sig_handler );
 
eval {
   my $h = set_sig_handler( 'ALRM' ,sub { die "connect failed" ; } );
   eval {
      alarm(2); #implement 2 second time out
      $dbh = DBI->connect("dbi:Oracle:$dbn" ... );
      alarm(0);
   };
   alarm(0);
   die $@ if $@;
}; #original signal handler restored here when $h goes out of scope
if ( $@ ) ....

 #eval {$dbh1 = DBI->connect( "dbi:Oracle://$dbip:1521/$dbname", $dbuser, $dbpass ) or die "Cannot conenct db: $DBI::errstr
";};

        eval {
          my $h = set_sig_handler( 'ALRM' ,sub { die "connect failed" ; } );
         eval {
            alarm(10); #implement 10 second time out
            $dbh1 = DBI->connect( "dbi:Oracle://$dbip:1521/$dbname", $dbuser, $dbpass ) or die "Cannot conenct db: $DBI::errstr
";
            alarm(0);
              };
             alarm(0);
             die $@ if $@;
       }; #original signal handler restored here when $h goes out of scope
原文地址:https://www.cnblogs.com/hzcya1995/p/13348856.html