Perl
DBD/DBI:如果程序被分叉,則崩潰
如果未設置 $crash 參數,則以下程序有效:
$ perl example mysql://:tange@/tange/mytable dburl mysql://:tange@/tange/mytable databasedriver mysql user password tange host port database tange table mytable query run DROP TABLE IF EXISTS mytable; run CREATE TABLE mytable (Seq INT, Exitval INT ); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?);
如果設置了 $crash,bzip2 將通過 open3 執行,數據通過分叉程序發送,這會使 DBD/DBI 崩潰:
$ perl example mysql://:tange@/tange/mytable 1 dburl mysql://:tange@/tange/mytable databasedriver mysql user password tange host port database tange table mytable query run DROP TABLE IF EXISTS mytable; run CREATE TABLE mytable (Seq INT, Exitval INT ); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); Orig: As bzip2:BZh9rE8P� 1 run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); DBD::mysql::st execute failed: MySQL server has gone away at example line 157. DBD::mysql::st execute failed: MySQL server has gone away at example line 157.
如果使用 Postgresql,這也是正確的:
$ perl example pg:////mytable dburl pg:////mytable databasedriver pg user password host port database table mytable query run DROP TABLE IF EXISTS mytable; run CREATE TABLE mytable (Seq INT, Exitval INT ); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?);
並設置了 $crash:
$ perl example pg:////mytable 1 dburl pg:////mytable databasedriver pg user password host port database table mytable query run DROP TABLE IF EXISTS mytable; run CREATE TABLE mytable (Seq INT, Exitval INT ); run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); Orig: As bzip2:BZh9rE8P� 1 run INSERT INTO mytable (Seq,Exitval) VALUES (?,?); DBD::Pg::st execute failed: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. at example line 157. DBD::Pg::st execute failed: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. at example line 157.
為什麼?有解決方法嗎?
對我來說 open3 和 fork 與 DBD/DBI 完全無關。
#!/usr/bin/perl use IPC::Open3; my $sql = SQL->new(shift); my $crash = shift; $Global::debug = "all"; $sql->create_table(); $sql->insert_records(2); $crash and print length string_zip("abc"),"\n"; $sql->insert_records(3); sub string_zip { # Pipe string through 'cmd' my $cmd = shift; my($zipin_fh, $zipout_fh,@base64); ::open3($zipin_fh,$zipout_fh,">&STDERR","bzip2 -9"); if(fork) { close $zipin_fh; @base64 = <$zipout_fh>; close $zipout_fh; } else { close $zipout_fh; print $zipin_fh @_; close $zipin_fh; exit; } ::debug("zip","Orig:@_\nAs bzip2:@base64\n"); return @base64; } sub undef_if_empty { if(defined($_[0]) and $_[0] eq "") { return undef; } return $_[0]; } sub debug { # Uses: # $Global::debug # %Global::fd # Returns: N/A print @_[1..$#_]; } package SQL; sub new { my $class = shift; my $dburl = shift; $Global::use{"DBI"} ||= eval "use DBI; 1;"; my %options = parse_dburl($dburl); my %driveralias = ("sqlite" => "SQLite", "sqlite3" => "SQLite", "pg" => "Pg", "postgres" => "Pg", "postgresql" => "Pg"); my $driver = $driveralias{$options{'databasedriver'}} || $options{'databasedriver'}; my $database = $options{'database'}; my $host = $options{'host'} ? ";host=".$options{'host'} : ""; my $port = $options{'port'} ? ";port=".$options{'port'} : ""; my $dsn = "DBI:$driver:dbname=$database$host$port"; my $userid = $options{'user'}; my $password = $options{'password'};; my $dbh = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) or die $DBI::errstr; return bless { 'dbh' => $dbh, 'max_number_of_args' => undef, 'table' => $options{'table'}, }, ref($class) || $class; } sub parse_dburl { my $url = shift; my %options = (); # sql:mysql://[[user][:password]@][host][:port]/[database[/table][?sql query]] if($url=~m!(?:sql:)? # You can prefix with 'sql:' ((?:oracle|ora|mysql|pg|postgres|postgresql)(?:s|ssl|)| (?:sqlite|sqlite2|sqlite3)):// # Databasedriver ($1) (?: ([^:@/][^:@]*|) # Username ($2) (?: :([^@]*) # Password ($3) )? @)? ([^:/]*)? # Hostname ($4) (?: : ([^/]*)? # Port ($5) )? (?: / ([^/?]*)? # Database ($6) )? (?: / ([^?]*)? # Table ($7) )? (?: \? (.*)? # Query ($8) )? !ix) { $options{databasedriver} = ::undef_if_empty(lc(uri_unescape($1))); $options{user} = ::undef_if_empty(uri_unescape($2)); $options{password} = ::undef_if_empty(uri_unescape($3)); $options{host} = ::undef_if_empty(uri_unescape($4)); $options{port} = ::undef_if_empty(uri_unescape($5)); $options{database} = ::undef_if_empty(uri_unescape($6)); $options{table} = ::undef_if_empty(uri_unescape($7)); $options{query} = ::undef_if_empty(uri_unescape($8)); ::debug("sql","dburl $url\n"); ::debug("sql","databasedriver ",$options{databasedriver}, " user ", $options{user}, " password ", $options{password}, " host ", $options{host}, " port ", $options{port}, " database ", $options{database}, " table ",$options{table}," query ",$options{query}, "\n"); } else { ::error("$url is not a valid DBURL"); exit 255; } return %options; } sub uri_unescape { # Copied from http://cpansearch.perl.org/src/GAAS/URI-1.55/URI/Escape.pm # to avoid depending on URI::Escape # This section is (C) Gisle Aas. # Note from RFC1630: "Sequences which start with a percent sign # but are not followed by two hexadecimal characters are reserved # for future extension" my $str = shift; if (@_ && wantarray) { # not executed for the common case of a single argument my @str = ($str, @_); # need to copy foreach (@str) { s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; } return @str; } $str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg if defined $str; $str; } sub run { my $self = shift; my $stmt = shift; my $dbh = $self->{'dbh'}; ::debug("sql","run $stmt\n"); # Execute with the rest of the args - if any my $rv; my $sth; $sth = $dbh->prepare($stmt); $rv = $sth->execute(@_); return $sth; } sub table { my $self = shift; return $self->{'table'}; } sub create_table { my $self = shift; my $table = $self->table(); $self->run(qq(DROP TABLE IF EXISTS $table;)); $self->run(qq{CREATE TABLE $table (Seq INT, Exitval INT }. qq{);}); } sub insert_records { my $self = shift; my $seq = shift; my $record_ref = shift; my $table = $self->table(); $self->run("INSERT INTO $table (Seq,Exitval) ". "VALUES (?,?);", $seq, -1000); }
當子程序或父程序退出時,它會關閉其數據庫句柄和關聯的套接字,並且在伺服器端,相應的後端也會退出。
從那時起,如果其他(仍然存在的)客戶端程序嘗試使用數據庫句柄,發送查詢將失敗,MySQL 伺服器已消失或 postgres伺服器意外關閉連接。這些消息似乎非常正確地描述了發生的事情。
主要的解決方法是呼叫
DBI->connect()
之後fork
而不是以任何方式在程序之間共享數據庫句柄。如果數據庫活動僅限於父級,您可以
AutoInactiveDestroy
提前設置數據庫句柄(從 DBI 1.614 開始)。這應該InactiveDestroy
在孩子中自動設置並解決問題。請參閱InactiveDestroy
DBI 文件:對於數據庫句柄,此屬性不會禁用對斷開連接方法的顯式呼叫,只會禁用來自 DESTROY 的隱式呼叫,如果句柄仍標記為活動,則會發生這種呼叫。
此屬性專門設計用於“派生”子程序的 Unix 應用程序。對於某些驅動程序,當子程序退出時,繼承句柄的銷毀會導致父程序中相應的句柄停止工作。
父程序或子程序中的任何一個(而不是兩者)都應在其所有共享句柄上將 InactiveDestroy 設置為 true。或者,並且優選地,可以在連接時在父級中設置“AutoInactiveDestroy”。