名字:
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