Skip Menu |

This queue is for tickets about the Mail-IMAPClient CPAN distribution.

Report information
The Basics
Id: 43414
Status: resolved
Priority: 0/
Queue: Mail-IMAPClient

People
Owner: Nobody in particular
Requestors: phil [...] perkpartners.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: SIGPIPE is not trapped for calls to syswrite()
Date: Wed, 18 Feb 2009 17:27:11 -0500
To: bug-Mail-IMAPClient [...] rt.cpan.org
From: Phil Lobbes <phil [...] perkpartners.com>
Mail::IMAPClient 3.x (and 2.x) can sometimes exit quietly because under certain conditions a SIGPIPE may be raised that is not trapped by the library. In theory, there are likely only a few places in the code when this might happen so the a patch is pretty straight forward. The most obvious handling is to ignore SIGPIPE and let the application handle EPIPE errors in other more "normal" ways. The UNIX Socket FAQ recommendation is to ignore SIGPIPE and to handle errno values ($! for Perl) instead. Reference: #2.19 of comp.unix.programmer Unix-socket-faq for network programming http://www.faqs.org/faqs/unix-faq/socket/ Below is a patch against Mail::IMAPClient 3.14 which affects migrate() and _send_bytes(). I am still in the process of doing my own testing, but I suspect this patch should be sufficient to cover the cases I have seen. There is also one minor (related) fix where LastError was using $@ instead of $! when reporting an error on sending a command. Phil --- IMAPClient.pm.ORIG 2009-02-16 08:15:52.000000000 -0500 +++ IMAPClient.pm 2009-02-18 17:10:48.000000000 -0500 @@ -19,7 +19,7 @@ use Carp qw(carp); use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); -use Errno qw/EAGAIN/; +use Errno qw/EAGAIN EPIPE/; use List::Util qw/first min max sum/; use MIME::Base64; use File::Spec (); @@ -679,6 +679,8 @@ my $fromSock = $self->Socket; my $bufferSize = $self->Buffer || 4096; + local $SIG{PIPE} = "IGNORE"; # avoid SIGPIPE on syswrite + unless(eval {$peer->IsConnected} ) { $self->LastError("Invalid or unconnected " . ref($self) . " object used as target for migrate. $@"); @@ -872,6 +874,10 @@ $temperrs = 0; } + if($! == EPIPE) + { $self->LastError("Error sending data to server: $!"); + return undef; + } if($! == EAGAIN || $ret==0) { if(defined $maxagain && $temperrs++ > $maxagain) { $self->LastError("Persistent '$!' errors"); @@ -916,9 +922,13 @@ # Now let's send a <CR><LF> to the peer to signal end of APPEND cmd: { my $wroteSoFar = 0; my $fromBuffer = "\r\n"; - $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0 - until $wroteSoFar >= 2; - + until($wroteSoFar >= 2) { + $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0; + if($! == EPIPE) + { $self->LastError("Error sending data to server: $!"); + return undef; + } + } } # Finally, let's get the new message's UID from the peer: @@ -1258,6 +1268,8 @@ my $maxagain = $self->Maxtemperrors || 10; undef $maxagain if $maxagain eq 'unlimited'; + local $SIG{PIPE} = "IGNORE"; # avoid SIGPIPE on syswrite + while($total < length $$byteref) { my $written = syswrite($self->Socket, $$byteref, length($$byteref)-$total, $total); @@ -2345,7 +2357,7 @@ my $bytes = $self->_send_line($command.$text."\r\n"); unless(defined $bytes) - { $self->LastError("Error sending command '$command': $@"); + { $self->LastError("Error sending command '$command': $!"); return undef; }
So far so good, however, in ADDITION to the patch above I would like to add the following patch to have us set the State to Unconnected when EPIPE occurs. I also added an Unconnected when an EOF is encountered (this case woud be related to bug #43415 ... sorry for the somewhat mixed but still simple patch): --- IMAPClient.pm.ORIG 2009-02-19 17:08:24.000000000 -0500 +++ IMAPClient.pm 2009-02-20 10:35:23.000000000 -0500 @@ -1290,6 +1290,11 @@ next; } + # Unconnected might be apropos for more than just EPIPE? + if($! == EPIPE) + { $self->State(Unconnected); + } + return undef; # no luck } @@ -1362,6 +1367,7 @@ if(defined $ret && $ret == 0) # Caught EOF... { my $msg = "Socket closed while reading data from server.\r\n"; $self->LastError($msg); + $self->State(Unconnected); $self->_record($transno, [ $self->_next_index($transno), "ERROR","$transno * NO $msg"]); return undef;
Attached is an updated patch (replacing the first two) that is working well for me. It logs well and properly marks the object as disconnected in the normal use cases. NOTE: I don't use the migrate() code so I have not tested that ... in that code I suppose there is a chance $self->State(Unconnected) could also need/want a $peer->State(Unconnected). Any chance a new release is coming soon with all the latest patches? I am sure I'll have a few more minor things coming, but I'm finding the latest fixes nice to have.
--- IMAPClient.pm.ORIG 2009-02-19 18:26:30.343750000 -0500 +++ IMAPClient.pm 2009-02-24 10:28:05.609375000 -0500 @@ -19,7 +19,7 @@ use Carp qw(carp); use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); -use Errno qw/EAGAIN/; +use Errno qw/EAGAIN EPIPE/; use List::Util qw/first min max sum/; use MIME::Base64; use File::Spec (); @@ -679,6 +679,8 @@ my $fromSock = $self->Socket; my $bufferSize = $self->Buffer || 4096; + local $SIG{PIPE} = "IGNORE"; # avoid SIGPIPE on syswrite + unless(eval {$peer->IsConnected} ) { $self->LastError("Invalid or unconnected " . ref($self) . " object used as target for migrate. $@"); @@ -872,6 +874,11 @@ $temperrs = 0; } + if($! == EPIPE) + { $self->LastError("Error sending data to server: $!"); + $self->State(Unconnected); + return undef; + } if($! == EAGAIN || $ret==0) { if(defined $maxagain && $temperrs++ > $maxagain) { $self->LastError("Persistent '$!' errors"); @@ -916,9 +923,14 @@ # Now let's send a <CR><LF> to the peer to signal end of APPEND cmd: { my $wroteSoFar = 0; my $fromBuffer = "\r\n"; - $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0 - until $wroteSoFar >= 2; - + until($wroteSoFar >= 2) { + $wroteSoFar += syswrite($toSock,$fromBuffer,2-$wroteSoFar,$wroteSoFar)||0; + if($! == EPIPE) + { $self->LastError("Error sending data to server: $!"); + $self->State(Unconnected); + return undef; + } + } } # Finally, let's get the new message's UID from the peer: @@ -1258,6 +1270,8 @@ my $maxagain = $self->Maxtemperrors || 10; undef $maxagain if $maxagain eq 'unlimited'; + local $SIG{PIPE} = "IGNORE"; # avoid SIGPIPE on syswrite + while($total < length $$byteref) { my $written = syswrite($self->Socket, $$byteref, length($$byteref)-$total, $total); @@ -1278,6 +1292,12 @@ next; } + # Unconnected might be apropos for more than just EPIPE? + if($! == EPIPE) + { $self->LastError("Error sending data to server: $!"); + $self->State(Unconnected); + } + return undef; # no luck } @@ -1351,6 +1371,7 @@ if(defined $ret && $ret == 0) # Caught EOF... { my $msg = "Socket closed while reading data from server.\r\n"; $self->LastError($msg); + $self->State(Unconnected); $self->_record($transno, [ $self->_next_index($transno), "ERROR","$transno * NO $msg"]); return undef; @@ -2345,7 +2366,8 @@ my $bytes = $self->_send_line($command.$text."\r\n"); unless(defined $bytes) - { $self->LastError("Error sending command '$command': $@"); + { $command =~ s/\r\n$//; + $self->LastError("Error sending command '$command': $@"); return undef; }
excellent patch. Will be included in 3.16