Subject: | Net::SMTP (or rather Cmd?) does not handle interrupted system calls |
When one of system calls (select, sysread) is interrupted by SIGCHLD Net::SMTP does not retry it and simply fails.
It seems there was work done to fix that problem for syswrite already in https://github.com/steve-m-hay/perl-libnet/pull/24 but other system calls are still broken.
This resulted in rare, non-deterministic failures in our email-sending program.
I believe this issue is behind problems "upstream" like this: https://rt.cpan.org/Ticket/Display.html?id=43104
Attached:
* sleepy_server.pl - sleeps for 10 seconds before acknowledging that it received "\r\n.\r\n". It listens on localhos:9025
* client.pl - which installs SIGCHLD handler and forks a child process that only sleeps for 2 seconds and then attempts to send email via localhost:9025
In this setup SIGCHLD is delivered when Net::Cmd is performing select() call and thus select fails with EINTR
Subject: | client.pl |
use strict;
use Net::SMTP;
$SIG{CHLD} = sub { warn "GOT SIGCHLD" };
if ( fork() ) {
} else {
sleep 2;
exit 0;
}
my $smtp = Net::SMTP->new('localhost', Port => 9025, Debug => 1);
$smtp->mail("foobar");
$smtp->to('postmaster');
$smtp->data();
$smtp->datasend("To: postmaster\n");
$smtp->datasend("\n");
$smtp->datasend("A simple test message\n");
# Server is configured to sleep 10 seconds before replying
$smtp->dataend() or die "DATAEND failed: ".$smtp->message;
$smtp->quit;
Subject: | sleepy_server.pl |
use strict;
use Net::Server::Mail::SMTP;
use IO::Socket::INET;
my $server = new IO::Socket::INET Listen => 1, LocalPort => 9025;
warn "Listening on port 9025\n";
my $conn;
while ($conn = $server->accept) {
warn "Connected to: ",$conn->peerhost(),"\n";
my $smtpd = new Net::Server::Mail::SMTP socket => $conn;
$smtpd->set_callback('banner' => \&banner);
$smtpd->set_callback('HELO' => \&helo);
$smtpd->set_callback('MAIL' => \&mail);
$smtpd->set_callback('RCPT' => \&rcpt);
$smtpd->set_callback('DATA-INIT' => \&data_init);
$smtpd->set_callback('DATA-PART' => \&data_part);
$smtpd->set_callback('DATA' => \&data_end);
$smtpd->set_callback('QUIT' => \&quit);
warn "Entering SMTP processing loop...\n";
$smtpd->process;
# cleanup
$smtpd = undef;
$conn = undef;
}
exit(0);
## subs
sub banner {
my $session = shift;
warn "CMD: banner\n";
return(0, 220, '[A] sleepy-server');
}
sub helo {
my $session = shift;
my $hostname = shift;
warn "CMD: helo $hostname\n";
return (1,250, "[B] helo $hostname");
}
sub mail {
my $session = shift;
my $address = shift;
warn "CMD: mail from $address\n";
return (1, 250, "[C] mail from $address");
}
sub rcpt {
my $session = shift;
my $address = shift;
warn "CMD: rcpt to $address\n";
return (1, 250, "[D] rcpt to $address");
}
sub data_init {
my $session = shift;
warn "CMD: data_init\n";
return (1, 354, '[E] data_init');
}
sub data_part {
my $session = shift;
my $chunk = shift; # ref to buffer
warn "CMD: data_part\n";
return (1);
}
sub data_end {
my $session = shift;
warn "CMD: data_end\n";
#
# Sleepy part
#
sleep 10;
return (1, 250, '[F] data_end');
}
sub quit {
my $session = shift;
warn "CMD: quit\n";
return (1, 221, '[G] quit');
}