| # ====================================================================== |
| # |
| # Copyright (C) 2000 Lincoln D. Stein |
| # Slightly modified by Paul Kulchenko to work on multiple platforms |
| # Formatting changed to match the layout layed out in Perl Best Practices |
| # (by Damian Conway) by Martin Kutter in 2008 |
| # |
| # ====================================================================== |
| |
| package IO::SessionData; |
| |
| use strict; |
| use Carp; |
| use IO::SessionSet; |
| use vars '$VERSION'; |
| $VERSION = 1.02; |
| |
| use constant BUFSIZE => 3000; |
| |
| BEGIN { |
| my @names = qw(EWOULDBLOCK EAGAIN EINPROGRESS); |
| my %WOULDBLOCK = |
| (eval {require Errno} |
| ? map { |
| Errno->can($_) |
| ? (Errno->can($_)->() => 1) |
| : (), |
| } @names |
| : () |
| ), |
| (eval {require POSIX} |
| ? map { |
| POSIX->can($_) && eval { POSIX->can($_)->() } |
| ? (POSIX->can($_)->() => 1) |
| : () |
| } @names |
| : () |
| ); |
| |
| sub WOULDBLOCK { $WOULDBLOCK{$_[0]+0} } |
| } |
| |
| # Class method: new() |
| # Create a new IO::SessionData object. Intended to be called from within |
| # IO::SessionSet, not directly. |
| sub new { |
| my $pack = shift; |
| my ($sset,$handle,$writeonly) = @_; |
| # make the handle nonblocking (but check for 'blocking' method first) |
| # thanks to Jos Clijmans <jos.clijmans@recyfin.be> |
| $handle->blocking(0) if $handle->can('blocking'); |
| my $self = bless { |
| outbuffer => '', |
| sset => $sset, |
| handle => $handle, |
| write_limit => BUFSIZE, |
| writeonly => $writeonly, |
| choker => undef, |
| choked => 0, |
| },$pack; |
| $self->readable(1) unless $writeonly; |
| return $self; |
| } |
| |
| # Object method: handle() |
| # Return the IO::Handle object corresponding to this IO::SessionData |
| sub handle { |
| return shift->{handle}; |
| } |
| |
| # Object method: sessions() |
| # Return the IO::SessionSet controlling this object. |
| sub sessions { |
| return shift->{sset}; |
| } |
| |
| # Object method: pending() |
| # returns number of bytes pending in the out buffer |
| sub pending { |
| return length shift->{outbuffer}; |
| } |
| |
| # Object method: write_limit([$bufsize]) |
| # Get or set the limit on the size of the write buffer. |
| # Write buffer will grow to this size plus whatever extra you write to it. |
| sub write_limit { |
| my $self = shift; |
| return defined $_[0] |
| ? $self->{write_limit} = $_[0] |
| : $self->{write_limit}; |
| } |
| |
| # set a callback to be called when the contents of the write buffer becomes larger |
| # than the set limit. |
| sub set_choke { |
| my $self = shift; |
| return defined $_[0] |
| ? $self->{choker} = $_[0] |
| : $self->{choker}; |
| } |
| |
| # Object method: write($scalar) |
| # $obj->write([$data]) -- append data to buffer and try to write to handle |
| # Returns number of bytes written, or 0E0 (zero but true) if data queued but not |
| # written. On other errors, returns undef. |
| sub write { |
| my $self = shift; |
| return unless my $handle = $self->handle; # no handle |
| return unless defined $self->{outbuffer}; # no buffer for queued data |
| |
| $self->{outbuffer} .= $_[0] if defined $_[0]; |
| |
| my $rc; |
| if ($self->pending) { # data in the out buffer to write |
| local $SIG{PIPE}='IGNORE'; |
| # added length() to make it work on Mac. Thanks to Robin Fuller <rfuller@broadjump.com> |
| $rc = syswrite($handle,$self->{outbuffer},length($self->{outbuffer})); |
| |
| # able to write, so truncate out buffer apropriately |
| if ($rc) { |
| substr($self->{outbuffer},0,$rc) = ''; |
| } |
| elsif (WOULDBLOCK($!)) { # this is OK |
| $rc = '0E0'; |
| } |
| else { # some sort of write error, such as a PIPE error |
| return $self->bail_out($!); |
| } |
| } |
| else { |
| $rc = '0E0'; # nothing to do, but no error either |
| } |
| |
| $self->adjust_state; |
| |
| # Result code is the number of bytes successfully transmitted |
| return $rc; |
| } |
| |
| # Object method: read($scalar,$length [,$offset]) |
| # Just like sysread(), but returns the number of bytes read on success, |
| # 0EO ("0 but true") if the read would block, and undef on EOF and other failures. |
| sub read { |
| my $self = shift; |
| return unless my $handle = $self->handle; |
| my $rc = sysread($handle,$_[0],$_[1],$_[2]||0); |
| return $rc if defined $rc; |
| return '0E0' if WOULDBLOCK($!); |
| return; |
| } |
| |
| # Object method: close() |
| # Close the session and remove it from the monitored list. |
| sub close { |
| my $self = shift; |
| unless ($self->pending) { |
| $self->sessions->delete($self); |
| CORE::close($self->handle); |
| } |
| else { |
| $self->readable(0); |
| $self->{closing}++; # delayed close |
| } |
| } |
| |
| # Object method: adjust_state() |
| # Called periodically from within write() to control the |
| # status of the handle on the IO::SessionSet's IO::Select sets |
| sub adjust_state { |
| my $self = shift; |
| |
| # make writable if there's anything in the out buffer |
| $self->writable($self->pending > 0); |
| |
| # make readable if there's no write limit, or the amount in the out |
| # buffer is less than the write limit. |
| $self->choke($self->write_limit <= $self->pending) if $self->write_limit; |
| |
| # Try to close down the session if it is flagged |
| # as in the closing state. |
| $self->close if $self->{closing}; |
| } |
| |
| # choke gets called when the contents of the write buffer are larger |
| # than the limit. The default action is to inactivate the session for further |
| # reading until the situation is cleared. |
| sub choke { |
| my $self = shift; |
| my $do_choke = shift; |
| return if $self->{choked} == $do_choke; # no change in state |
| if (ref $self->set_choke eq 'CODE') { |
| $self->set_choke->($self,$do_choke); |
| } |
| else { |
| $self->readable(!$do_choke); |
| } |
| $self->{choked} = $do_choke; |
| } |
| |
| # Object method: readable($flag) |
| # Flag the associated IO::SessionSet that we want to do reading on the handle. |
| sub readable { |
| my $self = shift; |
| my $is_active = shift; |
| return if $self->{writeonly}; |
| $self->sessions->activate($self,'read',$is_active); |
| } |
| |
| # Object method: writable($flag) |
| # Flag the associated IO::SessionSet that we want to do writing on the handle. |
| sub writable { |
| my $self = shift; |
| my $is_active = shift; |
| $self->sessions->activate($self,'write',$is_active); |
| } |
| |
| # Object method: bail_out([$errcode]) |
| # Called when an error is encountered during writing (such as a PIPE). |
| # Default behavior is to flush all buffered outgoing data and to close |
| # the handle. |
| sub bail_out { |
| my $self = shift; |
| my $errcode = shift; # save errorno |
| delete $self->{outbuffer}; # drop buffered data |
| $self->close; |
| $! = $errcode; # restore errno |
| return; |
| } |
| |
| 1; |